From 76c19377c1feaf7c095200ddcfb81c1812c7ed9d Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 11 Mar 2021 15:12:56 +0100 Subject: [PATCH 01/93] Temporary add pcloud-sdk-java as project dependency as long as https://github.com/pCloud/pcloud-sdk-java/pull/9 is not merged in upstream --- .gitmodules | 3 +++ .idea/vcs.xml | 1 + data/build.gradle | 1 + pcloud-sdk-java | 1 + settings.gradle | 4 +++- 5 files changed, 9 insertions(+), 1 deletion(-) create mode 160000 pcloud-sdk-java diff --git a/.gitmodules b/.gitmodules index 32f48167..393a652c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "subsampling-scale-image-view"] path = subsampling-scale-image-view url = https://github.com/SailReal/subsampling-scale-image-view.git +[submodule "pcloud-sdk-java"] + path = pcloud-sdk-java + url = https://github.com/SailReal/pcloud-sdk-java diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 286a07d2..16b33dfd 100755 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/data/build.gradle b/data/build.gradle index 7740b19c..ec7c580f 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -88,6 +88,7 @@ dependencies { implementation project(':domain') implementation project(':util') implementation project(':msa-auth-for-android') + implementation project(':pcloud-sdk-java') // cryptomator implementation dependencies.cryptolib diff --git a/pcloud-sdk-java b/pcloud-sdk-java new file mode 160000 index 00000000..55a32f4a --- /dev/null +++ b/pcloud-sdk-java @@ -0,0 +1 @@ +Subproject commit 55a32f4aab45c102861e87998dca2c3070df1ce2 diff --git a/settings.gradle b/settings.gradle index d0e47c23..950d766e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,4 @@ -include ':generator', ':presentation', ':generator-api', ':domain', ':data', ':util', ':subsampling-image-view', ':msa-auth-for-android' +include ':generator', ':presentation', ':generator-api', ':domain', ':data', ':util', ':subsampling-image-view', ':msa-auth-for-android', ':pcloud-sdk-java-root', ':pcloud-sdk-java' project(':subsampling-image-view').projectDir = file(new File(rootDir, 'subsampling-scale-image-view/library')) +project(':pcloud-sdk-java-root').projectDir = file(new File(rootDir, 'pcloud-sdk-java')) +project(':pcloud-sdk-java').projectDir = file(new File(rootDir, 'pcloud-sdk-java/java-core')) From aca3edbfa96719a02d2c29a2cf70bf1f9505fdab Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 11 Mar 2021 17:02:06 +0100 Subject: [PATCH 02/93] Update to latest version of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 55a32f4a..c1b3a8ae 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 55a32f4aab45c102861e87998dca2c3070df1ce2 +Subproject commit c1b3a8ae9e656c2ddcdb8737d8c52260d870c8e4 From 253aab651e0096662fe18f49e6f213ca0687fa21 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Sun, 14 Mar 2021 12:05:35 +0100 Subject: [PATCH 03/93] Add pcloud-sdk-android project as sub module --- pcloud-sdk-java | 2 +- presentation/build.gradle | 1 + settings.gradle | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index c1b3a8ae..d1be3998 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit c1b3a8ae9e656c2ddcdb8737d8c52260d870c8e4 +Subproject commit d1be3998526032a3324376ee0ba6fcb18f9a1319 diff --git a/presentation/build.gradle b/presentation/build.gradle index 81a1ea40..805ca2f8 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -118,6 +118,7 @@ dependencies { implementation project(':util') implementation project(':domain') implementation project(':data') + implementation project(':pcloud-sdk-android') // dagger kapt dependencies.daggerCompiler diff --git a/settings.gradle b/settings.gradle index 950d766e..66b721f7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,5 @@ -include ':generator', ':presentation', ':generator-api', ':domain', ':data', ':util', ':subsampling-image-view', ':msa-auth-for-android', ':pcloud-sdk-java-root', ':pcloud-sdk-java' +include ':generator', ':presentation', ':generator-api', ':domain', ':data', ':util', ':subsampling-image-view', ':msa-auth-for-android', ':pcloud-sdk-java-root', ':pcloud-sdk-java', ':pcloud-sdk-android' project(':subsampling-image-view').projectDir = file(new File(rootDir, 'subsampling-scale-image-view/library')) project(':pcloud-sdk-java-root').projectDir = file(new File(rootDir, 'pcloud-sdk-java')) project(':pcloud-sdk-java').projectDir = file(new File(rootDir, 'pcloud-sdk-java/java-core')) +project(':pcloud-sdk-android').projectDir = file(new File(rootDir, 'pcloud-sdk-java/android')) From 217d9e191a3cd5c210716b862b00f0c2069a5f13 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Sun, 14 Mar 2021 14:09:44 +0100 Subject: [PATCH 04/93] Fix build -.- --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index d1be3998..40492c53 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit d1be3998526032a3324376ee0ba6fcb18f9a1319 +Subproject commit 40492c53dc330b51f4415c37c62ceda0e5ab91e1 From f824ead9b358e4555268a2bf9fb0235b28362797 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Mon, 15 Mar 2021 16:08:56 +0100 Subject: [PATCH 05/93] Fix crash off pcloud-sdk on older API levels --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 40492c53..a303c5fc 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 40492c53dc330b51f4415c37c62ceda0e5ab91e1 +Subproject commit a303c5fc41124efc09cc1e1c5f875625e99db3f6 From 9abf1f6eb6b53ec303decda2ac916f5cd69e7655 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Wed, 17 Mar 2021 17:05:03 +0100 Subject: [PATCH 06/93] Apply latest changes of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index a303c5fc..3ea51722 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit a303c5fc41124efc09cc1e1c5f875625e99db3f6 +Subproject commit 3ea517220203d7a092ff8109b8c5c209d1d22493 From 0f42ddd9648383942f462fb445aa1736a4a06585 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 19 Mar 2021 12:47:04 +0100 Subject: [PATCH 07/93] Rename webdavUrl to url in CloudEntity --- data/build.gradle | 2 +- .../cryptomator/data/db/DatabaseUpgrades.java | 6 ++- .../org/cryptomator/data/db/Upgrade4To5.kt | 40 +++++++++++++++++++ .../data/db/entities/CloudEntity.java | 16 ++++---- .../data/db/mappers/CloudEntityMapper.java | 4 +- 5 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt diff --git a/data/build.gradle b/data/build.gradle index ec7c580f..2187a71f 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -74,7 +74,7 @@ android { } greendao { - schemaVersion 4 + schemaVersion 5 } configurations.all { diff --git a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java index 1b3725ee..b116cb04 100644 --- a/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java +++ b/data/src/main/java/org/cryptomator/data/db/DatabaseUpgrades.java @@ -22,13 +22,15 @@ class DatabaseUpgrades { Upgrade0To1 upgrade0To1, // Upgrade1To2 upgrade1To2, // Upgrade2To3 upgrade2To3, // - Upgrade3To4 upgrade3To4) { + Upgrade3To4 upgrade3To4, // + Upgrade4To5 upgrade4To5) { availableUpgrades = defineUpgrades( // upgrade0To1, // upgrade1To2, // upgrade2To3, // - upgrade3To4); + upgrade3To4, // + upgrade4To5); } private static Comparator reverseOrder() { diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt new file mode 100644 index 00000000..dbb0f155 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt @@ -0,0 +1,40 @@ +package org.cryptomator.data.db + +import org.greenrobot.greendao.database.Database +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +internal class Upgrade4To5 @Inject constructor() : DatabaseUpgrade(4, 5) { + + override fun internalApplyTo(db: Database, origin: Int) { + db.beginTransaction() + try { + changeWebdavUrlInCloudEntityToUrl(db) + db.setTransactionSuccessful() + } finally { + db.endTransaction() + } + } + + private fun changeWebdavUrlInCloudEntityToUrl(db: Database) { + Sql.alterTable("CLOUD_ENTITY").renameTo("CLOUD_ENTITY_OLD").executeOn(db) + + Sql.createTable("CLOUD_ENTITY") // + .id() // + .requiredText("TYPE") // + .optionalText("ACCESS_TOKEN") // + .optionalText("URL") // + .optionalText("USERNAME") // + .optionalText("WEBDAV_CERTIFICATE") // + .executeOn(db); + + Sql.insertInto("CLOUD_ENTITY") // + .select("_id", "TYPE", "ACCESS_TOKEN", "WEBDAV_URL", "USERNAME", "WEBDAV_CERTIFICATE") // + .columns("_id", "TYPE", "ACCESS_TOKEN", "URL", "USERNAME", "WEBDAV_CERTIFICATE") // + .from("CLOUD_ENTITY_OLD") // + .executeOn(db) + + Sql.dropTable("CLOUD_ENTITY_OLD").executeOn(db) + } +} diff --git a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java index 21551729..0ce2c8a1 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java +++ b/data/src/main/java/org/cryptomator/data/db/entities/CloudEntity.java @@ -16,18 +16,18 @@ public class CloudEntity extends DatabaseEntity { private String accessToken; - private String webdavUrl; + private String url; private String username; private String webdavCertificate; - @Generated(hash = 2078985174) - public CloudEntity(Long id, @NotNull String type, String accessToken, String webdavUrl, String username, String webdavCertificate) { + @Generated(hash = 361171073) + public CloudEntity(Long id, @NotNull String type, String accessToken, String url, String username, String webdavCertificate) { this.id = id; this.type = type; this.accessToken = accessToken; - this.webdavUrl = webdavUrl; + this.url = url; this.username = username; this.webdavCertificate = webdavCertificate; } @@ -60,12 +60,12 @@ public class CloudEntity extends DatabaseEntity { this.id = id; } - public String getWebdavUrl() { - return webdavUrl; + public String getUrl() { + return url; } - public void setWebdavUrl(String webdavUrl) { - this.webdavUrl = webdavUrl; + public void setUrl(String url) { + this.url = url; } public String getUsername() { diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java index 39118d2b..f369ba5c 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java @@ -54,7 +54,7 @@ public class CloudEntityMapper extends EntityMapper { case WEBDAV: return aWebDavCloudCloud() // .withId(entity.getId()) // - .withUrl(entity.getWebdavUrl()) // + .withUrl(entity.getUrl()) // .withUsername(entity.getUsername()) // .withPassword(entity.getAccessToken()) // .withCertificate(entity.getWebdavCertificate()) // @@ -87,7 +87,7 @@ public class CloudEntityMapper extends EntityMapper { break; case WEBDAV: result.setAccessToken(((WebDavCloud) domainObject).password()); - result.setWebdavUrl(((WebDavCloud) domainObject).url()); + result.setUrl(((WebDavCloud) domainObject).url()); result.setUsername(((WebDavCloud) domainObject).username()); result.setWebdavCertificate(((WebDavCloud) domainObject).certificate()); break; From 91a93d675ae504f9ac0703414563967c9e1c6d2d Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 19 Mar 2021 14:48:45 +0100 Subject: [PATCH 08/93] Update pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 3ea51722..72c0c292 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 3ea517220203d7a092ff8109b8c5c209d1d22493 +Subproject commit 72c0c292ecad6a11a2c01c2a724ea2f37b186bf8 From 40c679e7dd6af2d2b5edc1231f1148769fbbf260 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 19 Mar 2021 17:49:15 +0100 Subject: [PATCH 09/93] Fix database update crash when Dao object changed --- .../java/org/cryptomator/data/db/Sql.java | 55 +++++++++++++++++++ .../org/cryptomator/data/db/Upgrade2To3.kt | 23 +++++--- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/Sql.java b/data/src/main/java/org/cryptomator/data/db/Sql.java index 5f703a0a..1fc488a4 100644 --- a/data/src/main/java/org/cryptomator/data/db/Sql.java +++ b/data/src/main/java/org/cryptomator/data/db/Sql.java @@ -1,6 +1,7 @@ package org.cryptomator.data.db; import android.content.ContentValues; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import org.greenrobot.greendao.database.Database; @@ -49,6 +50,10 @@ class Sql { return new SqlUpdateBuilder(tableName); } + public static SqlQueryBuilder query(String table) { + return new SqlQueryBuilder(table); + } + public static Criterion eq(final String value) { return (column, whereClause, whereArgs) -> { whereClause.append('"').append(column).append("\" = ?"); @@ -91,6 +96,56 @@ class Sql { void appendTo(String column, StringBuilder whereClause, List whereArgs); } + public static class SqlQueryBuilder { + + private final String tableName; + private final StringBuilder whereClause = new StringBuilder(); + private final List whereArgs = new ArrayList<>(); + + private List columns = new ArrayList<>(); + private String groupBy; + private String having; + private String limit; + + public SqlQueryBuilder(String tableName) { + this.tableName = tableName; + } + + public SqlQueryBuilder columns(List columns) { + this.columns = columns; + return this; + } + + public SqlQueryBuilder where(String column, Criterion criterion) { + if (whereClause.length() > 0) { + whereClause.append(" AND "); + } + criterion.appendTo(column, whereClause, whereArgs); + return this; + } + + public SqlQueryBuilder groupBy(String groupBy) { + this.groupBy = groupBy; + return this; + } + + public SqlQueryBuilder having(String having) { + this.having = having; + return this; + } + + public SqlQueryBuilder limit(String limit) { + this.limit = limit; + return this; + } + + public Cursor executeOn(Database wrapped) { + SQLiteDatabase db = unwrap(wrapped); + return db.query(tableName, columns.toArray(new String[columns.size()]), whereClause.toString(), whereArgs.toArray(new String[whereArgs.size()]), groupBy, having, limit); + } + + } + public static class SqlUpdateBuilder { private final String tableName; diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt index 465b5cd6..183f7333 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt @@ -2,10 +2,8 @@ package org.cryptomator.data.db import android.content.Context import android.content.SharedPreferences -import org.cryptomator.data.db.entities.CloudEntityDao import org.cryptomator.util.crypto.CredentialCryptor import org.greenrobot.greendao.database.Database -import org.greenrobot.greendao.internal.DaoConfig import javax.inject.Inject import javax.inject.Singleton @@ -13,16 +11,23 @@ import javax.inject.Singleton internal class Upgrade2To3 @Inject constructor(private val context: Context) : DatabaseUpgrade(2, 3) { override fun internalApplyTo(db: Database, origin: Int) { - val clouds = CloudEntityDao(DaoConfig(db, CloudEntityDao::class.java)).loadAll() db.beginTransaction() try { - clouds.filter { cloud -> cloud.type == "DROPBOX" || cloud.type == "ONEDRIVE" } // - .map { - Sql.update("CLOUD_ENTITY") // - .where("TYPE", Sql.eq(it.type)) // - .set("ACCESS_TOKEN", Sql.toString(encrypt(if (it.type == "DROPBOX") it.accessToken else onedriveToken()))) // - .executeOn(db) + Sql.query("CLOUD_ENTITY") + .columns(listOf("ACCESS_TOKEN")) + .where("TYPE", Sql.eq("DROPBOX")) + .executeOn(db).use { + if(it.moveToFirst()) { + Sql.update("CLOUD_ENTITY") + .set("ACCESS_TOKEN", Sql.toString(encrypt(it.getString(it.getColumnIndex("ACCESS_TOKEN"))))) + .where("TYPE", Sql.eq("DROPBOX")); + } } + + Sql.update("CLOUD_ENTITY") + .set("ACCESS_TOKEN", Sql.toString(encrypt(onedriveToken()))) + .where("TYPE", Sql.eq("ONEDRIVE")); + db.setTransactionSuccessful() } finally { db.endTransaction() From d448b37f43cdc904a942e887b35715851f8ab2ab Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 19 Mar 2021 18:12:05 +0100 Subject: [PATCH 10/93] Recreate VAULT_ENTITY when changing CLOUD_ENTITY Otherwise ForeignKeyBehaviour.ON_DELETE_SET_NULL causes vaults no longer have a cloud-id --- .../org/cryptomator/data/db/Upgrade4To5.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt index dbb0f155..9f8b72e7 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade4To5.kt @@ -35,6 +35,39 @@ internal class Upgrade4To5 @Inject constructor() : DatabaseUpgrade(4, 5) { .from("CLOUD_ENTITY_OLD") // .executeOn(db) + recreateVaultEntity(db) + Sql.dropTable("CLOUD_ENTITY_OLD").executeOn(db) } + + private fun recreateVaultEntity(db: Database) { + Sql.alterTable("VAULT_ENTITY").renameTo("VAULT_ENTITY_OLD").executeOn(db) + Sql.createTable("VAULT_ENTITY") // + .id() // + .optionalInt("FOLDER_CLOUD_ID") // + .optionalText("FOLDER_PATH") // + .optionalText("FOLDER_NAME") // + .requiredText("CLOUD_TYPE") // + .optionalText("PASSWORD") // + .optionalInt("POSITION") // + .foreignKey("FOLDER_CLOUD_ID", "CLOUD_ENTITY", Sql.SqlCreateTableBuilder.ForeignKeyBehaviour.ON_DELETE_SET_NULL) // + .executeOn(db) + + Sql.insertInto("VAULT_ENTITY") // + .select("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_ENTITY.TYPE") // + .columns("_id", "FOLDER_CLOUD_ID", "FOLDER_PATH", "FOLDER_NAME", "PASSWORD", "POSITION", "CLOUD_TYPE") // + .from("VAULT_ENTITY_OLD") // + .join("CLOUD_ENTITY", "VAULT_ENTITY_OLD.FOLDER_CLOUD_ID") // + .executeOn(db) + + Sql.dropIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID").executeOn(db) + + Sql.createUniqueIndex("IDX_VAULT_ENTITY_FOLDER_PATH_FOLDER_CLOUD_ID") // + .on("VAULT_ENTITY") // + .asc("FOLDER_PATH") // + .asc("FOLDER_CLOUD_ID") // + .executeOn(db) + + Sql.dropTable("VAULT_ENTITY_OLD").executeOn(db) + } } From 34022c96ab36fb3bc76a571d78a8ffef36b17f92 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Wed, 24 Mar 2021 12:20:03 +0100 Subject: [PATCH 11/93] Add pCloud license --- presentation/src/main/res/xml/licenses.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/presentation/src/main/res/xml/licenses.xml b/presentation/src/main/res/xml/licenses.xml index ab3f1415..78bd3a9c 100644 --- a/presentation/src/main/res/xml/licenses.xml +++ b/presentation/src/main/res/xml/licenses.xml @@ -113,6 +113,13 @@ android:action="android.intent.action.VIEW" android:data="https://github.com/rburgst/okhttp-digest/" /> + + + From a50152c01c6b55c9eab731362794d8c05abd4b07 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Wed, 24 Mar 2021 13:16:30 +0100 Subject: [PATCH 12/93] Update to latest version of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 72c0c292..42d0db86 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 72c0c292ecad6a11a2c01c2a724ea2f37b186bf8 +Subproject commit 42d0db864589a441b892be375e8d4eea2e363af9 From 118bcdb5e27e5e26ad30075c0854d33dc6a8bd4a Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Wed, 24 Mar 2021 14:32:29 +0100 Subject: [PATCH 13/93] Update to latest version of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 42d0db86..66221947 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 42d0db864589a441b892be375e8d4eea2e363af9 +Subproject commit 66221947fbccf5b6dd3ee03327a35ff93c6a2345 From 5b8e71a0c5cf808c67a19b36fc7becc339334c04 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Wed, 24 Mar 2021 16:08:27 +0100 Subject: [PATCH 14/93] Update to latest version of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 66221947..66a810ea 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 66221947fbccf5b6dd3ee03327a35ff93c6a2345 +Subproject commit 66a810eadc52d31bc7ecaee97e573909779a1400 From 62dbce191835a39b2e1af9f0b9afe71167c172e7 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Wed, 24 Mar 2021 19:01:41 +0100 Subject: [PATCH 15/93] Update to latest version of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 66a810ea..eb3226a6 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 66a810eadc52d31bc7ecaee97e573909779a1400 +Subproject commit eb3226a663b949f4be5b6a356c52cf1205079386 From cf0f447b4dae0804b712ccc878f6f7a1583097e9 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 25 Mar 2021 11:18:12 +0100 Subject: [PATCH 16/93] Update to latest version of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index eb3226a6..466772f0 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit eb3226a663b949f4be5b6a356c52cf1205079386 +Subproject commit 466772f0538953074cfafe11e9fa5c3bc8e84a5e From f3342943f9111e5b84db7f89c6dfbda34c823304 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 25 Mar 2021 15:08:13 +0100 Subject: [PATCH 17/93] Change clouds from grid to list view --- .../presentation/model/CloudTypeModel.kt | 44 +++++++++------ .../ui/activity/ChooseCloudServiceActivity.kt | 2 +- .../adapter/BiometricAuthSettingsAdapter.kt | 2 +- .../ui/adapter/CloudConnectionListAdapter.kt | 2 +- .../presentation/ui/adapter/CloudsAdapter.kt | 6 +- .../ui/adapter/SharedLocationsAdapter.kt | 2 +- .../presentation/ui/adapter/VaultsAdapter.kt | 7 ++- .../CloudConnectionSettingsBottomSheet.kt | 2 +- .../bottomsheet/SettingsVaultBottomSheet.kt | 2 +- .../ui/fragment/ChooseCloudServiceFragment.kt | 4 +- .../cloud_type_dropbox_large.png | Bin 2159 -> 0 bytes .../cloud_type_google_drive_large.png | Bin 5619 -> 0 bytes .../cloud_type_onedrive_large.png | Bin 1851 -> 0 bytes .../drawable-mdpi/cloud_type_webdav_large.png | Bin 9383 -> 0 bytes .../src/main/res/drawable-mdpi/dropbox.png | Bin 0 -> 516 bytes ...oud_type_dropbox.png => dropbox_vault.png} | Bin .../drawable-mdpi/dropbox_vault_selected.png | Bin 0 -> 489 bytes .../main/res/drawable-mdpi/google_drive.png | Bin 0 -> 962 bytes ...oogle_drive.png => google_drive_vault.png} | Bin .../google_drive_vault_selected.png | Bin 0 -> 518 bytes .../src/main/res/drawable-mdpi/local_fs.png | Bin 0 -> 298 bytes ...rage_type_local.png => local_fs_vault.png} | Bin .../drawable-mdpi/local_fs_vault_selected.png | Bin 0 -> 459 bytes .../src/main/res/drawable-mdpi/onedrive.png | Bin 0 -> 510 bytes ...d_type_onedrive.png => onedrive_vault.png} | Bin .../drawable-mdpi/onedrive_vault_selected.png | Bin 0 -> 564 bytes .../src/main/res/drawable-mdpi/pcloud.png | Bin 0 -> 525 bytes .../main/res/drawable-mdpi/pcloud_vault.png | Bin 0 -> 912 bytes .../drawable-mdpi/pcloud_vault_selected.png | Bin 0 -> 551 bytes .../storage_type_local_large.png | Bin 1972 -> 0 bytes .../src/main/res/drawable-mdpi/webdav.png | Bin 0 -> 1341 bytes ...cloud_type_webdav.png => webdav_vault.png} | Bin .../drawable-mdpi/webdav_vault_selected.png | Bin 0 -> 651 bytes .../cloud_type_dropbox_large.png | Bin 3046 -> 0 bytes .../cloud_type_google_drive_large.png | Bin 12882 -> 0 bytes .../cloud_type_onedrive_large.png | Bin 3532 -> 0 bytes .../cloud_type_webdav_large.png | Bin 26743 -> 0 bytes .../src/main/res/drawable-xhdpi/dropbox.png | Bin 0 -> 998 bytes ...oud_type_dropbox.png => dropbox_vault.png} | Bin .../drawable-xhdpi/dropbox_vault_selected.png | Bin 0 -> 883 bytes .../main/res/drawable-xhdpi/google_drive.png | Bin 0 -> 2109 bytes ...oogle_drive.png => google_drive_vault.png} | Bin .../google_drive_vault_selected.png | Bin 0 -> 1000 bytes .../src/main/res/drawable-xhdpi/local_fs.png | Bin 0 -> 541 bytes ...rage_type_local.png => local_fs_vault.png} | Bin .../local_fs_vault_selected.png | Bin 0 -> 848 bytes .../src/main/res/drawable-xhdpi/onedrive.png | Bin 0 -> 890 bytes ...d_type_onedrive.png => onedrive_vault.png} | Bin .../onedrive_vault_selected.png | Bin 0 -> 1131 bytes .../src/main/res/drawable-xhdpi/pcloud.png | Bin 0 -> 1098 bytes .../main/res/drawable-xhdpi/pcloud_vault.png | Bin 0 -> 1923 bytes .../drawable-xhdpi/pcloud_vault_selected.png | Bin 0 -> 1143 bytes .../storage_type_local_large.png | Bin 4034 -> 0 bytes .../src/main/res/drawable-xhdpi/webdav.png | Bin 0 -> 3157 bytes ...cloud_type_webdav.png => webdav_vault.png} | Bin .../drawable-xhdpi/webdav_vault_selected.png | Bin 0 -> 1337 bytes .../cloud_type_dropbox_large.png | Bin 5266 -> 0 bytes .../cloud_type_google_drive_large.png | Bin 20926 -> 0 bytes .../cloud_type_onedrive_large.png | Bin 5680 -> 0 bytes .../cloud_type_webdav_large.png | Bin 53102 -> 0 bytes .../src/main/res/drawable-xxhdpi/dropbox.png | Bin 0 -> 1508 bytes ...oud_type_dropbox.png => dropbox_vault.png} | Bin .../dropbox_vault_selected.png | Bin 0 -> 1385 bytes .../main/res/drawable-xxhdpi/google_drive.png | Bin 0 -> 3394 bytes ...oogle_drive.png => google_drive_vault.png} | Bin .../google_drive_vault_selected.png | Bin 0 -> 1388 bytes .../src/main/res/drawable-xxhdpi/local_fs.png | Bin 0 -> 827 bytes ...rage_type_local.png => local_fs_vault.png} | Bin .../local_fs_vault_selected.png | Bin 0 -> 1259 bytes .../src/main/res/drawable-xxhdpi/onedrive.png | Bin 0 -> 1266 bytes ...d_type_onedrive.png => onedrive_vault.png} | Bin .../onedrive_vault_selected.png | Bin 0 -> 1685 bytes .../src/main/res/drawable-xxhdpi/pcloud.png | Bin 0 -> 1623 bytes .../main/res/drawable-xxhdpi/pcloud_vault.png | Bin 0 -> 2796 bytes .../drawable-xxhdpi/pcloud_vault_selected.png | Bin 0 -> 1720 bytes .../storage_type_local_large.png | Bin 6110 -> 0 bytes .../src/main/res/drawable-xxhdpi/webdav.png | Bin 0 -> 5538 bytes ...cloud_type_webdav.png => webdav_vault.png} | Bin .../drawable-xxhdpi/webdav_vault_selected.png | Bin 0 -> 2040 bytes .../src/main/res/layout/item_cloud.xml | 53 ++++++++++++------ 80 files changed, 80 insertions(+), 46 deletions(-) delete mode 100644 presentation/src/main/res/drawable-mdpi/cloud_type_dropbox_large.png delete mode 100644 presentation/src/main/res/drawable-mdpi/cloud_type_google_drive_large.png delete mode 100644 presentation/src/main/res/drawable-mdpi/cloud_type_onedrive_large.png delete mode 100644 presentation/src/main/res/drawable-mdpi/cloud_type_webdav_large.png create mode 100644 presentation/src/main/res/drawable-mdpi/dropbox.png rename presentation/src/main/res/drawable-mdpi/{cloud_type_dropbox.png => dropbox_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-mdpi/dropbox_vault_selected.png create mode 100644 presentation/src/main/res/drawable-mdpi/google_drive.png rename presentation/src/main/res/drawable-mdpi/{cloud_type_google_drive.png => google_drive_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-mdpi/google_drive_vault_selected.png create mode 100644 presentation/src/main/res/drawable-mdpi/local_fs.png rename presentation/src/main/res/drawable-mdpi/{storage_type_local.png => local_fs_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-mdpi/local_fs_vault_selected.png create mode 100644 presentation/src/main/res/drawable-mdpi/onedrive.png rename presentation/src/main/res/drawable-mdpi/{cloud_type_onedrive.png => onedrive_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-mdpi/onedrive_vault_selected.png create mode 100644 presentation/src/main/res/drawable-mdpi/pcloud.png create mode 100644 presentation/src/main/res/drawable-mdpi/pcloud_vault.png create mode 100644 presentation/src/main/res/drawable-mdpi/pcloud_vault_selected.png delete mode 100644 presentation/src/main/res/drawable-mdpi/storage_type_local_large.png create mode 100644 presentation/src/main/res/drawable-mdpi/webdav.png rename presentation/src/main/res/drawable-mdpi/{cloud_type_webdav.png => webdav_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-mdpi/webdav_vault_selected.png delete mode 100644 presentation/src/main/res/drawable-xhdpi/cloud_type_dropbox_large.png delete mode 100644 presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive_large.png delete mode 100644 presentation/src/main/res/drawable-xhdpi/cloud_type_onedrive_large.png delete mode 100644 presentation/src/main/res/drawable-xhdpi/cloud_type_webdav_large.png create mode 100644 presentation/src/main/res/drawable-xhdpi/dropbox.png rename presentation/src/main/res/drawable-xhdpi/{cloud_type_dropbox.png => dropbox_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xhdpi/dropbox_vault_selected.png create mode 100644 presentation/src/main/res/drawable-xhdpi/google_drive.png rename presentation/src/main/res/drawable-xhdpi/{cloud_type_google_drive.png => google_drive_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xhdpi/google_drive_vault_selected.png create mode 100644 presentation/src/main/res/drawable-xhdpi/local_fs.png rename presentation/src/main/res/drawable-xhdpi/{storage_type_local.png => local_fs_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xhdpi/local_fs_vault_selected.png create mode 100644 presentation/src/main/res/drawable-xhdpi/onedrive.png rename presentation/src/main/res/drawable-xhdpi/{cloud_type_onedrive.png => onedrive_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xhdpi/onedrive_vault_selected.png create mode 100644 presentation/src/main/res/drawable-xhdpi/pcloud.png create mode 100644 presentation/src/main/res/drawable-xhdpi/pcloud_vault.png create mode 100644 presentation/src/main/res/drawable-xhdpi/pcloud_vault_selected.png delete mode 100644 presentation/src/main/res/drawable-xhdpi/storage_type_local_large.png create mode 100644 presentation/src/main/res/drawable-xhdpi/webdav.png rename presentation/src/main/res/drawable-xhdpi/{cloud_type_webdav.png => webdav_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xhdpi/webdav_vault_selected.png delete mode 100644 presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox_large.png delete mode 100644 presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive_large.png delete mode 100644 presentation/src/main/res/drawable-xxhdpi/cloud_type_onedrive_large.png delete mode 100644 presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav_large.png create mode 100644 presentation/src/main/res/drawable-xxhdpi/dropbox.png rename presentation/src/main/res/drawable-xxhdpi/{cloud_type_dropbox.png => dropbox_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xxhdpi/dropbox_vault_selected.png create mode 100644 presentation/src/main/res/drawable-xxhdpi/google_drive.png rename presentation/src/main/res/drawable-xxhdpi/{cloud_type_google_drive.png => google_drive_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xxhdpi/google_drive_vault_selected.png create mode 100644 presentation/src/main/res/drawable-xxhdpi/local_fs.png rename presentation/src/main/res/drawable-xxhdpi/{storage_type_local.png => local_fs_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xxhdpi/local_fs_vault_selected.png create mode 100644 presentation/src/main/res/drawable-xxhdpi/onedrive.png rename presentation/src/main/res/drawable-xxhdpi/{cloud_type_onedrive.png => onedrive_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xxhdpi/onedrive_vault_selected.png create mode 100644 presentation/src/main/res/drawable-xxhdpi/pcloud.png create mode 100644 presentation/src/main/res/drawable-xxhdpi/pcloud_vault.png create mode 100644 presentation/src/main/res/drawable-xxhdpi/pcloud_vault_selected.png delete mode 100644 presentation/src/main/res/drawable-xxhdpi/storage_type_local_large.png create mode 100644 presentation/src/main/res/drawable-xxhdpi/webdav.png rename presentation/src/main/res/drawable-xxhdpi/{cloud_type_webdav.png => webdav_vault.png} (100%) create mode 100644 presentation/src/main/res/drawable-xxhdpi/webdav_vault_selected.png diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt index edb4256a..077793d6 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt @@ -7,42 +7,54 @@ enum class CloudTypeModel(builder: Builder) { CRYPTO(Builder("CRYPTO", R.string.cloud_names_crypto)), // DROPBOX(Builder("DROPBOX", R.string.cloud_names_dropbox) // - .withCloudImageResource(R.drawable.cloud_type_dropbox) // - .withCloudImageLargeResource(R.drawable.cloud_type_dropbox_large)), // + .withCloudImageResource(R.drawable.dropbox) // + .withVaultImageResource(R.drawable.dropbox_vault) // + .withVaultSelectedImageResource(R.drawable.dropbox_vault_selected)), // GOOGLE_DRIVE(Builder("GOOGLE_DRIVE", R.string.cloud_names_google_drive) // - .withCloudImageResource(R.drawable.cloud_type_google_drive) // - .withCloudImageLargeResource(R.drawable.cloud_type_google_drive_large)), // + .withCloudImageResource(R.drawable.google_drive) // + .withVaultImageResource(R.drawable.google_drive_vault) // + .withVaultSelectedImageResource(R.drawable.google_drive_vault_selected)), // ONEDRIVE(Builder("ONEDRIVE", R.string.cloud_names_onedrive) // - .withCloudImageResource(R.drawable.cloud_type_onedrive) // - .withCloudImageLargeResource(R.drawable.cloud_type_onedrive_large)), // + .withCloudImageResource(R.drawable.onedrive) // + .withVaultImageResource(R.drawable.onedrive_vault) // + .withVaultSelectedImageResource(R.drawable.onedrive_vault_selected)), // WEBDAV(Builder("WEBDAV", R.string.cloud_names_webdav) // - .withCloudImageResource(R.drawable.cloud_type_webdav) // - .withCloudImageLargeResource(R.drawable.cloud_type_webdav_large) // + .withCloudImageResource(R.drawable.webdav) // + .withVaultImageResource(R.drawable.webdav_vault) // + .withVaultSelectedImageResource(R.drawable.webdav_vault_selected) // .withMultiInstances()), // LOCAL(Builder("LOCAL", R.string.cloud_names_local_storage) // - .withCloudImageResource(R.drawable.storage_type_local) // - .withCloudImageLargeResource(R.drawable.storage_type_local_large) // + .withCloudImageResource(R.drawable.local_fs) // + .withVaultImageResource(R.drawable.local_fs_vault) // + .withVaultSelectedImageResource(R.drawable.local_fs_vault_selected) // .withMultiInstances()); val cloudName: String = builder.cloudName val displayNameResource: Int = builder.displayNameResource val cloudImageResource: Int = builder.cloudImageResource - val cloudImageLargeResource: Int = builder.cloudImageLargeResource + val vaultImageResource: Int = builder.vaultImageResource + val vaultSelectedImageResource: Int = builder.vaultSelectedImageResource val isMultiInstance: Boolean = builder.multiInstances private class Builder(val cloudName: String, val displayNameResource: Int) { var cloudImageResource = 0 - var cloudImageLargeResource = 0 + var vaultImageResource = 0 + var vaultSelectedImageResource = 0 var multiInstances = false - fun withCloudImageResource(cloudImageResource: Int): Builder { - this.cloudImageResource = cloudImageResource + fun withCloudImageResource(cloudImageLargeResource: Int): Builder { + this.cloudImageResource = cloudImageLargeResource return this } - fun withCloudImageLargeResource(cloudImageLargeResource: Int): Builder { - this.cloudImageLargeResource = cloudImageLargeResource + fun withVaultImageResource(vaultImageResource: Int): Builder { + this.vaultImageResource = vaultImageResource + return this + } + + fun withVaultSelectedImageResource(vaultSelectedImageResource: Int): Builder { + this.vaultSelectedImageResource = vaultSelectedImageResource return this } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt index f9d0caf9..c5e1bc3c 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ChooseCloudServiceActivity.kt @@ -29,7 +29,7 @@ class ChooseCloudServiceActivity : BaseActivity(), ChooseCloudServiceView { setSupportActionBar(toolbar) } - override fun createFragment(): Fragment? = ChooseCloudServiceFragment() + override fun createFragment(): Fragment = ChooseCloudServiceFragment() override fun getCustomMenuResource(): Int = R.menu.menu_cloud_services diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt index ee154fa0..85bd708b 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/BiometricAuthSettingsAdapter.kt @@ -48,7 +48,7 @@ constructor() : RecyclerViewBaseAdapter throw IllegalStateException("Cloud model is not binded in the view") } - iv_cloud_image.setImageResource(cloudModel.cloudType().cloudImageResource) + iv_cloud_image.setImageResource(cloudModel.cloudType().vaultImageResource) change_cloud.setOnClickListener { callback?.onChangeCloudClicked(cloudModel) dismiss() diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/SettingsVaultBottomSheet.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/SettingsVaultBottomSheet.kt index 5451e47a..84f4c96a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/SettingsVaultBottomSheet.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/SettingsVaultBottomSheet.kt @@ -31,7 +31,7 @@ class SettingsVaultBottomSheet : BaseBottomSheet-_QF#@8x;k=Y9WqQ=J^nNQorEbVg&thH-v+{Tcx|H?AAaiwjdV3l0qy!4{7 z(7z;mq|n8cKI|GjU!Lz@GB6@U>~~?R`4rRwiV~FniJ>*MP}L62qHaCB#nK_uBG#Ht$>5YvGR5Wh z14xgg4SlKMOgGCkD5QfgF>}7bhHD@-Pdmm1YMTF8gFUnEt|1c@$YjX`xdoY*%B}!@ zkk@=bDobvHHT+gjFicXuZvzQs@t80QHu$bjR=Pb)tIRoSx*|;c7tHZti81H_z-P;b z1g?~o`IE79R(E%43p8^|yNF@K6rm`@$ey;btmt+vLysg?_qdiNkWApPa%n&>%)K3^ zRjbRPy;dMgM8@TCV(@NQpkR|eVgPmirH=$`#B}mEW%NKv86#wLj5Y0iy`XpYnRGt5XPaCVu-`{uCm7S^yvLR!W15(?mbG( zhC>-YsqZ&PP$kIx; z{m$+hWsxHd+7nf5?^)IOupGhc?nAQJ2$I;H+!!lHs3M?Mf7MjxW05NCVw)%T-4!6p zrT&N}`PY@AIR%$7Bka(qS166}Z47~Y?sSfkY- z`q1~m{3d0mGy}IqsugjQ#~!FXvah@z-7|!jS7a zl4zk{JzP$`8GiKfac*DaC8PQ{odCObX{}*8M>DI1k-^t{hO74L#g`^*va-N8*1gh~ z7|EE5@bEZ`JITb=z4&-Xl8naI+bs<@>2_t}<=EV*ig8RH9WJiM8}IQxoVjLdQkR@H ztFJnQTApNNhi znXL;!KBxN&gWco-D57yzO&}Sj!_vwNc3VRe|Fq1T@?z5}GB!#(=5Aeo{crnk?uu)Itg_gV$|GJy(IuXVr3DWsRFGg!;T9th7 zG*-tg^Fd9qK9WXh>bl&t;89*{$_)YvMi~PraT40AXk&g50Z$od?-QN77UDx$l!dm- z>$YLpagQ=3>hh$cwn4jT&kjwYWUy?dog^t|C`!0{BPdbo@G3K8pS$QPmLnUHzhPEJ z&7_{pdefZr?xVxdAL&1dXM)5M`~Ybdfd5UAhbl<7yZ2pWV)m=ZM8+I02JGlCCifGm z4nJ6%dcQYda-wV4{gxC@k5feq>~drJ*I&cbKDpb-hpyo$A=8s`qsZNJd(K_rsmXb0 zu>9an(9vU~egAPxD~=jbO^)r@L|`oKIp&*YnM_{M(fnI4y1(_PHWvOJzCe2|``Mca zBVPMF@=+&o@ z?wG*X=&kay^_}StJ2kne;8NeZYFf|^h)-x1NI$UK{`R4pjoz$Xw@}@?4~#*cUJp~r znp&My8aL*JJ=%yi@H#d0>M^jl$Pq-R@I*2cF+mGOPb2Hkz&oq{8eDq1VE*Rc>=PUA zAY^QdjQd;oRDvGp`IS$Ith55`#2+q)`^Asd|2A7mRBfwM<)uVJv21hjlq9~BLa23! zcN{Av-tWp#C{@QVn{T=9O^s@3&HcD`!nSs`Hd^0m#9B8NUG+K`2L#NX{onx%69j!j z2jJ9x4iD@Uo6Uh-$s|ISAbA7eEWylbDl;oZl2wGHe9wRFgO!FqRq+XF1HA`_fs|1D zgBVaG(1Dc{Pt*QvXr0>xQIfyF#siirgJqnfcxI>QQnlX(fe;9CBD`6kGls#`CCX`` zd?OMNK7TK6)C%+P0KT&8+5~KlCm=ZVe(Fg{f{qUG1(ZRs5nPvPXaLGA3wjf_r0ZW$ zW=GXKNO$Y+>`++a2D10NtUs%M;-Y~sW}=%{*CkSiY8O0$`xFWa zGkNHf=`PzGFdY$SSPDgTpLBZ2ODK?Ib9Dd~nE3y(E91?+)v{>K Rf$TjJfF0Jsw#tSW{|}U`{LcUY diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_google_drive_large.png b/presentation/src/main/res/drawable-mdpi/cloud_type_google_drive_large.png deleted file mode 100644 index 9279d5b96b1e78bf5a14d06e4757e027b4567ace..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5619 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 G4gU}J8r4Yv diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_onedrive_large.png b/presentation/src/main/res/drawable-mdpi/cloud_type_onedrive_large.png deleted file mode 100644 index b5504da668313d71f819a0742eb8f2d08118506f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1851 zcmV-B2gLY^P)sx^v}%h*}lw(Z?d*S4*( zZF{b5Yi!%rI?lWQv$}mN?6m4spAWw=&--*e_0ClK!si3X$4BvXhmRp2L0G9ked1;3 z;x`zK=dlM(kWMsXA6~#v{1*8p0PbhwGw6%8kQm#Dq4+#(69D%(6Sra)BtdrLZk%fZ zkUTY*i+V_sG+-`1XabNpMVN^uh)c9!9*Ru>;-?q-q8{Q>wYV2OOaNl*3)l|P#1?$U z1R#2T+=XU{W}0vtY!iU)$-?sx%{`3_6M)E+U<- z;!(#@X#&u;avXwq+yNAu0K8u|wn9?GW@N(z0DimzNiomCkCYEUKS+wYFVzF^b+kZI zR5QNn`2bkAZU~?TKgX506GQFmmv6PNSt9l|i^X@}j$v9DVuzROD5%iMJq$Mi9JsMfj8!0bl@W7>*`LGI1j*{)=Do zN&p^leE>N0k6{PIHMmFcUHz^XZWdcm?%Duw*yp1b;z~EIQCoxVAsFns0C2cA20%1G zEvyj#B`@n5fFtm`CID*qm;gx_Qw!g6QvgOl63W!Vcvl2~1H2xRaHbaaBs~Be+BeY* z@eFFgc1bM+FMmaP-T1Dg-M&IH?pr5WeK$(hfX$LUaEtQyypg+~#dzF=?;s6UEVcU` z99A9BvK;=Arx%@Zh%C_)I#vGm(|Uj)0!YV9Ed(xpQF`CJM6w5MQHSkT|HKa>4Zt#p;Q;%F zl@hwS0N9>aF*@vZO;j#Crns-$4C+^oN1nbbVvj8lHq`IE>ZWF*;W9ahj>wsd$*`pq`E{$KUnY7!iSszSmUjmx($-f?#|90{UE(r z3;mq|*y^3#eYaH1iTVQC9VH9DV%)3 z3@vW8a44EuxT&qj!WTiJ;lE>(A;r_o?ygxaJOQh{0DQa4yZc zJ>n!aW_Q=Q7EYp9X91{0-T!y@q3rH6b=}uYd(Az({UOSbH;Iu!G~c*LwyoYetZG9;!*5C6C@(W3A}>7_&WTsVnKb9Xd&d| zQ}}wjjIZK@$bl6v>XTGum;e~&5b&>d>3{+N003ZE|JKiOhBcS~2!H?xfB*=900@8p p2!H?xfB*=900@8p2!H?xz+J66OwkI8n_mC`002ovPDHLkV1h;yVDSI| diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_webdav_large.png b/presentation/src/main/res/drawable-mdpi/cloud_type_webdav_large.png deleted file mode 100644 index d55d555ec05d17f02874e95794fb1d54eef6bbc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9383 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 literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-mdpi/google_drive.png b/presentation/src/main/res/drawable-mdpi/google_drive.png new file mode 100644 index 0000000000000000000000000000000000000000..4a2f04d71732288d74d1bae9e9a8b64dfec893fa GIT binary patch literal 962 zcmV;z13mnSP)3#U9i2M{M*2OYoNdktTOXTde&s@|S@#wk`i$kp?q^1<)^+i3wHGf^kbQl4q5Lx;fAM zrJ=cU@Y{#Y-??TXgCQUQpc#A^9CSUb3YkGGOSa%b0?`9XaquVG{&0rhFB&43*KF!S zOwACpAb^Dw#(?c%ADFqHf`J*CYk0L+ zV$pp^{~_QDQ`?6(#&)!4*bA`Wv}eIS4-)6!3^p)M2df?3v8|8%UEt7lGarp>m1k#V z7BL~h!AJUD@k&+jgtlu*K=3@@_Dv!pxc7 zOgL+v^|i&heO@@caWWSe8`u&60wl(*zT>B}v77YjpQYT{eYudlpYH%c0MUI*ap0qrkp*lcwmkzA$EElOK>&5CD$t`bc@L8$U`2VLpVdpcb9q z+Vjedt=TAez{7KR^+zuzvt+Xe1aUK*9AM7nK^)W9YcA;_ZST*j=0qLP0yN+z2^QFE z#f2!0oC_Js1`DK$Q%(k#Vo@T3N+g1#UoX)T0G8bTeZAD;S2LIPz+_kLkBpjw;3+bj z*kMg_GAx)mk#e4Ef~EAZJd*dQ`N8}qRTPEmP69%iAXkBiELJMWVgMGHJRNgm@sHAM zG4qzstEI*hwheN39*8hiKm?*-A)#Hz0MatDv$xBuRpQoh@9m|0eRx<-IulCe(FP`Y#s_{o&|E>y>yrl_Pu=ywig84u&43-is z1Rw=SDIbV`AGMRXe%$+eGb{zMn-a8MY}+>a*h!x@wo@g25vPx;xQbcEwpGRYZ0+|y>z$oB6~Ahpd!N(Hjy3<> z0v#Juup77W3)Nx2a2tEV8Cy5cZ_&Xre1d1_6PCeAw{RdH!pqP@3^XmAhQIK#^cPcf zg>z61FGs2|S5-J1CGc{r6vLAXUBln(p5mW)+2A*I;|rG=lx;;l77K+VP@{SM@<^d= z@YlZz3kUHHZG_T&23y7s1gC0(4VVZEo$(Jlg~ImuqnR}|5ZtW^olY9O3RQ}Iwn8&& zcOZ1jCs2!_!U^;UXBDT@Q=fS^1EF6&fy0WSk$?0|VSO?h6!U&rJ{!FfeT9K*<1(+% zM97?09Rz-b`-MT>QJ9q5AmsX?TrQNq(qeQG1|1W!|FBK)SpG`oKg}`4q5dDc482kP zY4!*UW#}1K))a5C6_$aXD3!N`dgC978?YT;(A==_h;Wgdi9pK^-ygYdv|9FZRfzmuZfJdmV81wUR#h9ubarg#eW;*N0DzY|`YqAZHj2>T4I!eRUvA^-pY07*qo IM6N<$g8V7$`2YX_ literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-mdpi/local_fs.png b/presentation/src/main/res/drawable-mdpi/local_fs.png new file mode 100644 index 0000000000000000000000000000000000000000..6025d2e1722d05014c24b0a7425eb132f5c3f5c0 GIT binary patch literal 298 zcmV+_0oDGAP)#O4G+5HCOsTEyZH3WDkQ4!$r3^6ihI zumca@U^{zZVYzIw`M+uHM>Q;A#CV4R2lb!}3)nG&HWpzPnmEFY5HoQ=Gwk9FeRbWy z5ObXHQcjp-fQEXuk6Dz@Qo;>eNI+Y)Qw$p`!+}KPpb|9J!4(qI6_ze$;SEX18`ig2 z6%RPS5)r+ChA#D5z7k^gBBkZmqR#V|N zNZ4?mgfeOgcfs4zGJ-OEm2i*7X08Lu+Ls9(@JWq2$$%pYZD!1LkN~?Z>@v-F4T6VE zEh>bj;?wVen;NnK^>S0j%!Pp>=mU7*i;hR&77Xe5ooXJ{xvU@;#&p7Q3Y=mb1Z=9E z)yRqS8=jbkKhWcrEJ1t@{+YbTyfI}OjyZ{h6DBV;n2CN}3h4k_@pPTi`GagBkO{xR zW+~tqT*!n}dnujofM1EQ@|9i*fgUZ!++yzu*9MiZRPxar0%JvvWFJkxqOhSsxFwku zH1XF%z$|TgA%|5wezXQ*B>tXWTa-=2-&2kYvX*|pu561Dwy?zrRhy_#)01X}j1>|t zn5;Nv?y?)Hw2W0yiIt|01V>#j zKx}o;GL)p{UZm)B6sh~1U7mTu{fZ=n2bGvi*9cw+oz6gNzDEmjt5bA7iu6OTfq`hZ zscLwzF2N7E?^R?Peg&Dv-Z2Y>`#}wj;C!#4l!I>~=Zr@vw)`cEuXu$bOJ4B*W2fkQ z%B=+Vx`~Lrt|I;DyC^XKnG!KoaN%=goBYr&+FuFow+kihe;rvTKR}VC>ee`0{t9{L zK1G&^rort>aE4hZ^|0H>GwX?qh`FL%!CbMs`G69vY(%8q*N}1CJrr8>+9N>E5X=;nc3GatX?N$@>z#N3bk^PWAo!CdCR4FCWD07*qoM6N<$f>qA% ALjV8( literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_onedrive.png b/presentation/src/main/res/drawable-mdpi/onedrive_vault.png similarity index 100% rename from presentation/src/main/res/drawable-mdpi/cloud_type_onedrive.png rename to presentation/src/main/res/drawable-mdpi/onedrive_vault.png diff --git a/presentation/src/main/res/drawable-mdpi/onedrive_vault_selected.png b/presentation/src/main/res/drawable-mdpi/onedrive_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4bd83cb6cbdd196484578c4a0b78eea9ff49f5 GIT binary patch literal 564 zcmV-40?Yl0P)=i2Oz;GOdxa1(h{|)R8@xmr#p%|i37c=n@;-JUxz)&xz z{s82~SnR+-%ts|O^kSl2Lg@Fv1c>1ac47z$7!7-i9Hc@VBzzB?1krF3m5~p%kQWuP z5u!Xr5p0H7FzI{XE{Jjq#V``@A$W@^$cxsv3;AU$YN7!a;~hl9&hLTuAxb~AgJ`$` z4+>x;YNH_9qZdk|4xU1k`ys|cFN5t64fik>v4}t%cBnCpM?Z-2!3vDQ8OT45pfjS- z0?Y6W0}zctcm=^O6t@EJLzJpmf-hK!1Vo|(4ndT|Xn>MfgR_W+DEF;NGYzFs4n;8r zcObBQ#w_GR1teLMW->(ih&04v0S@A@7xtkCqHxSu7UshLfbpn+aD;dv3Qe)YTKH;f zbJZn;{cc$gh|7BD9!_ z$E=FKh45vFmo9r>2;0ICk15y#UFz?_6vV>{0`LtCW%5G&dK-@b0000bfEuU}DwGD(@D`^a zEI>!t5H?_(%WT|-@EcXJ7s43`Cs5comILP?;}(RH&aja!z>a3bRS4VA0m6OAxQOJ= z3dwK)a`}P#s0g`aHtQ_H8Voh#10noD4*bFo$fbo7><#(&i+$)GB=`s)u)vJ>f{dED zgTIi=U?;d83Sd{DV36y-4Kk|Z4&&}DL)1fFw7@PX z0@K~!yXml?Fy7-buHp+6p%X~w`#e>~d_2M*C{F)y4>Rz8oDAfEQ7{Ssy28u{?-&*h P00000NkvXXu0mjfRwUqv literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-mdpi/pcloud_vault.png b/presentation/src/main/res/drawable-mdpi/pcloud_vault.png new file mode 100644 index 0000000000000000000000000000000000000000..f3ccd64b402a96a5934fe1fbf6b5760ce14f0827 GIT binary patch literal 912 zcmV;B18@9^P)%g(ci+0IPSw1b?(=)2z9Uo3 zR=ZDcSRZQSX#QeQVA!jjr?aD;qj3(QjXoax6)}jt&rjZ<0?ST2S7*+&$ZY4YuD?&K z=W1wdbBvbumIy){eHcRwVv&Q~#{%Eg%+>04u5ekapRcF2gS7_cQ%L5Rs6BTzFua?*l6FzHB!wHeDe3If4hg*i-o~;Cs9jTg{8j0_?eh7d+v8 z8~6^^J!K^20D&(lc)}Zu>p(kf_I-PXlUX@iNpXX51XXYwefRAtd#^LY{w?qgj&s98 zgWSZ@=~A*Nwj}Ek8~UpGEBd183o$RA6b@IZOTi?8P6sCd;>}y zNu!Y7Q1Y$uBmdd}is+BTOksu?xlk~`lJWZf$|k2$K|>oulsl0}dinZnm_t@2*2GCS z;xk>ZzTp6$Qi&#uAq-GAT5r&l*3YPgwwTEO!Wd zQ#)TreDvv;j$g^A#+M8Wj1a>!j}ZAGmRa30)I*f0>w|?;MZyfUaA5K0?O7uo?*CmbwEAPlYnvHrsT0hT+FOFy>#NOooR=;PUX(J$@4pbb;v zLXItw21+d{_U$?l$3Jx>6>Z8I%ObC8Z(&R=TOEG@`*S@9P{}2gJTyQ5|8KABbG~;8 zLy5Q^LZ4BgF_xPG>w-k_x_NrihlZE$x7*iZa(fbcBfbJqaS#G=XU2LFTSX>y{tr~O#wj29tBn?-Y+P4x>UHX zgK_)w)WiGp;-4pYvUl(8&lB|iD}8{s;eFQ=LzRp0HUeJ)1}}Kt_lQ!(zk2Q?3LXMX zK2TWRUc3St7aAW7Lo9NT3y-Hhgy*@m?flJ;T~s7|A(r=-A+!m77()zVANml!4~(b& mZ(PFcm literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-mdpi/pcloud_vault_selected.png b/presentation/src/main/res/drawable-mdpi/pcloud_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..648a8f8ff2802d6fbd9a348931a67e523df8b43c GIT binary patch literal 551 zcmV+?0@(eDP)!9K96dt&Oa~l-G&E0X z4DuUr6n;VrQvT!k7|3FS7_g|&#GDJEh91ka)PhwG?;#TbZ&5G;kda7!o&sN*E0Ts3S%41y0> zfj&rJ5}IH+q7XEKx{wg#p?8OP?12Cb2)w{|bVeMTA?4aaUHEAY7>9V2p}l9oW0;E~ z2>N0X1Q9gHT5G@r)X@~;(Hc7;IEem`av>Z;eTYYMs3T!rnuQR@WNbn`NV&$?gOLzN zE$h;(Hh%if5Jw5NVNbaYXb*vFrtvJyho8biG=m^hwJ`^ujfd}iZNYhkuuueZpaGlX zwx`F?fIM;Alb{Hn18TrUR>T+^K*Sj97ox`EbAeViHq)3`geQ5PSy8S*_%$>`uQLzA pwlD@^4er8?`ggGggAj(m^dF|7!3vYrbjSbz002ovPDHLkV1lr9?hA>;7PCk(MZ-0BQ(O?2-0dK#3{1P2AqpyHS*bZL zXy$@hO7$#JP|?)1GD|Hd$I>L1rl#k-Ki>QIo%7E5?)}cW_x^Fux!<|pB`*&b1WXqO z004sMO7Pia=HH~oES zol>a-cFke!zIt}bC)%KN;{laHd-ung2%Yo)!B!PSHeqAEIV9J-{ynoxY$zVXV1?&3 zS(eN-mJ7Qt!*RSlAtyiTzOe~^46i~i!jL?3tNmdul^6mfPsNnE3-6+QdH@P#S1-d9 ze-}?Gg@JV;np*RvdcnKdZ566=AfL*#5MLJ0ovN9E$(x+>G|)G6$wpQl493F%ik;z} z!S{2V)SD*zbk7+FXCic9p-Ku!d@XXPkya~~HN(>OxsmY=@duYJ0tH$QDn85`(HoV+ zWcJVRt4;uaGT(>~c6`6L`}q{N`?`_>Dg^K0!J>vU*B}c_EirOBszbK(29fwZN>r)N zSv%L%VI~WF!X?gs+Xb>2tTGW1=#^iLfoC5n$9Kxl{0hhhyKxGg%i zZkskIGcNMXQg6pbUIV#(tO(&D~uc66)h>dcybmfmSqHn{{X_b`L`7C5**@?8} z0yV6?%Za;gdk%4F+;7mhL{uz}*=a^1?R)Zzqv7kh37;DC?!GRwr#+u@pV$kJLS8Cg zEwoxJ#}{*7clOfg-a!nSfwZnL&!^V0Ouzh^kxwP+exP541bC$tYYel!6K^A?z;ORKCkSlSskp>F#c-B;M+(zh zbfz;(Ud~>;BPLN+*8k${^4=?t@|>`? zD3UH)a%Gfs6h}*{4p9f+=4W@iywl0&6HTU;5`po@U-CnT(n9f`{mmb)QR)&KizV9D zsUZ{5$aVoeLThB|)#2eqF+wa8gTv=$?;_`ug3{|m>s1+i3HM7xM6G3LP&S7wb5b*z z@#wNyjblno;@f`iZ%&x8f>4AJ$Y8gitcS&*Nve+im5BMhCUN~Y1J2Vmd2joHS2pN$ zR*OdD`%Duyrth0^Hfnkn{uHO0llZ_WnEGlvNEE;w^d0KE?Yv-$2Mwo&@T+^p73wP96|e5)<2l;)UrCZDtZyt^E#mK&y2%gt|sS9 zW9gos`%EJz_E7Al)Oz~KQ9Ba)!)l9tc$?h|gXGEJ4+~>{o*Ph+S6KOsG$pqM*~R;r zME`B0m^&VpV?Spr#HJtkCFjZaGufEyFYn}6^vWZdPMi9>oLkbWL%s_}IN!p=SU>~o z@J{mjaXm*ih*7AUVqOnrjA_(WyG{FU&i2GwIO0&^*7Wdz3jO4%rec=>na1y~wT+_Z z(1cu48EBPAqgv8`BXGM?7mvG|KQL&^5h&tw5}rD4pv^iXi@QC0PZcmq-rVKr z09=;x@emK5(osDXF78GR1gK7izjzD#7_d>4cJ#^0_iGica_me;cVX(+iYDRo|9sS| zUI@;qOj}7{_So|2R;RAft+k*wAj<|_9F2+LO9WX2-;}fQObCkav8b$V=-Tc&TphuR zh2?V=fWhj}6W9iUUw_p}&;6FmoY*a!54s6mQe@Ro)_^tb>2Q%)6H8_j+Vk(}Dg4E% zWO!4z0GAYWnntGOu6*Q2X$KU>bk90qdg!A!ay-Z+(Th)k6Up|y z@LxA>w8{O4k8J4Z>Zx$KoTD!o!z=)VXegQxOU5IiO|xc9vxYx+jC`T7>2k?O+rIqp z!1u?Hh=u@;yXR&Qn&&vEM1!m-Fbo6!oE&)EP8g;+O(A0Vj%NNf>vg;RHoq`rJj7l@wsh zdvBtrw-3by37kCBfN4`Iv1|Wf+)8%h{dKExskI$jw(Vf~7ezTb(m($|4n-+O3xbA? zmI(!;_WC=U@$TlWaJ!rgjM}X~{=~1R&*Q~~a}h5sgdhm``p{8?@&l}>sITk{MLDK0 zh5{1tFrJ<{mCg6v?jI_m#^yE_vdZ#Ow6rC$WI;8~H8#_9;OvDaj0^ao5`v5*#~y}Z zRM!pk-%a7M@`>rbKz}MV1b(9KJobL`BPLf&f@PV=AD4&qYgXb)`*pN;biwO( z!O#t4GEy`v!?Y-cbzMhlAdQ(*tJvK3-8Fb_-fVVJTeE5zOv}Qlvkf@c(2N;VtMJIg zV$}Y0l5Q9R&qLER>MJxR!_)O=gp4RfWL1UV=f%?JpJnqeES!rSpMEg3ZpGTSvG}Do zn4(bDw3?}1Y-xkjA+UZlEt5s-R9(H-mZ7F;5ejJPI!cPhqp7tGtyiyM_7l^wd;eh^ zJzj^&m1Wqu_YhVtTf_>>f`vT?YFYDlb>Ct=NEL!U6GbUNG>zu5V{|}Wk7q=Q_j$dn zLRYWbh~B>2nDS@^UR|{mzh7#>wd>vZcGoBP?#OXGv*cyOA|W^g0iW;Ohj=uMa45(m zk7KGOL)UbBKtf1N6lEr#^rcWRfJi8aWM?<(&o^SzhP6yVw(mZOqsM+jRb?5|P})F& zTt9f8W5q&MiRXEyQVJn4+mL2NIeK>>%>Zeo(rucCiJ}Q{)co)ZYJWZnRV5G{0*VX9 z(}>(ZBG}qt#}LoqZYoW~8np$qV8sl>1ktqn&RA}BjLXY~X2Ot z6SfbjWtP!Ii~r&$g=Lx*wt#N8J5Q1%rlZ48`M;0tJ}kHg?rEiay}p1gpg%Y7s>|)3 z!f|}o%#EBkG1%-u-jB2?L$x$;7T|KZT5Y9`5YjL30&9!GiyJlu`x$gcw%!|h z{%LV&mIaRI@l>!18C^z35y5l3%5nG%|10|oz!_c51CDN700000NkvXXu0mjftzUtA literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-mdpi/cloud_type_webdav.png b/presentation/src/main/res/drawable-mdpi/webdav_vault.png similarity index 100% rename from presentation/src/main/res/drawable-mdpi/cloud_type_webdav.png rename to presentation/src/main/res/drawable-mdpi/webdav_vault.png diff --git a/presentation/src/main/res/drawable-mdpi/webdav_vault_selected.png b/presentation/src/main/res/drawable-mdpi/webdav_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..be58746c2f98efadc2c9fa5aa97bc3fc363a6ffb GIT binary patch literal 651 zcmV;60(AX}P)w;t0WNGZ^IYfCceIHaoGvtO--SI0P z!>K)@^Pw|}K@bD$@eQ3I-b8>P6K0`>3CMuFaNax;JJ9hB|3gp#i{MZN;=$Cy3B-nI z_)Q3g*@?ifz+({A3hVG4@gXWLZa~yj{wI#CM$6W4G}G zL$Mkg_4sDo#1i$vPHp(rqXdoMl+_i};nYb~)c_X^jqLJtG=N34Inp2_n&1IkFdVbX zlOQ$ThSLI^+P^JEnQ+)*lnHDTRVIu|`qqZ|8K_d@7+eS)L+X%jgs;H`(Y26ngl%v^ ljKY4q)z@RAP|yVV=_g1Ou4RNo23Y_A002ovPDHLkV1kSlB-Q`` literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_dropbox_large.png b/presentation/src/main/res/drawable-xhdpi/cloud_type_dropbox_large.png deleted file mode 100644 index 60b2e26d58b34621f0415f8d31c32276eaf9ed52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3046 zcmeHJ`8U*U8~JLZ0cozr26I`_p@#>wM1r`CRw;oO7M~I`_HGm54Ug2XnwU0000R8t9n= zz%hnA2C#t`O znLawW$HfUL{JmgkV6j?p5+m?v)3J%kd%IQS+;jM=ogn7F2zv^*G~ewfB_7W{oYT89 zYr0&@bB(`l&DQ3a+m|OoXIsQ6-~4W{MC|s-sE+=+ou*AK;H#vYJ2kd>Z3RocRD2u# zlOy@!MAnJ>CLio-$*!L}W&3}GFQP(rX#G=5VJ>YAFq&iKGVD_3po;#x*j)OU9?FYn_^-YfdU{!7F6HK#t$3R0TYgGc&1xevx1ywAt z?jy?-exJHVx$jnV^tud6y^u{O8S}-tDZR)Z>N4G95|Yfp>DBhJ4un%H%}Lu$5uh9& z@=qI*9Z_cHjQ04a9p~EU#zD`EzIkf=cYpFs@$7w zoX5P|-*1pLZCnV88>$;8Q#49`R#Eq2gl=l|{#Nwkj>vH^gY-;=dE;qhUj`V5S6ik; zO7Uz*^$;m4`WqqHuW{z%J905!ibET8vZP&{?8n_b(>^#$BP*kXp@2|p-Q zoc6>o#1bsNY^vpJ{6O$9Z=~#VQv%-6lZ9mUD)_V%$I5#XCc5J@R0&^snFe!YAGqvK z2eD~Ixui#I2dbzi;i7h6usWx!RC=LmOgY*X;xa7dE?Q8D-=;$k@}BTzS6DERmV%MZzWRWnV|KA;>bAx9a(## zX*>tCe!Qw%r}b@OP@~U}Xz=9o#}5!K8Udd}Ht|bKokQ&rZiT}-#6>X&{Vmh;v?G)u zk}^~!BVK~Vm|I-9b9(JDq$0(5smsLO>K7NMjL$`;%;LwTVS$$UCGPEmXYS#a=Y-GB zS@x`AZCDJ;7ag6`mo{}_*%+K7Z<`-6!#U}tfR}$$xbmZ56??d}b&;cCU9DF@FiTM) zNsFZ3kqIwLHMfjnx-l^Ur=O}g@y@d6;r{XCg7piH{EKPYQ~5&RRJ4z`mGL8kR4{#< z#%JJ5GfdfXvzspuP1}k=$fLObR^l~O+ZgQ)?D#|Z(`4-V? zf(ux(MG`a4%AmEL=JgbDS+lHvjgob}+rK&7&&MlybWR^agR6a|r2p*$Q*&f!_Sgmv z453oWSe`X58=rPcl29$`f^B@km1UGPOp5yD$n%Qaw>Dt2hb-uPccO`8jTMzcN0TnU zBG3Q+bz6`4P*D4Pf^Rlm{7^R02@h=|Jyo)Q#51To=ix{D!I!s727hH*u`blZZ-j)z zIaYVb>;KhYE0f@xE+(DBzT026vfC__lV>!4uitYm_G0IpgbVi7zQW$TV9<>r{AGNd zn~6kpZQpD@oKfUp`;Cs+A$P;}b9&Yf*T4gQ&XK&)7^;=jS>pps%xTqNko+@h2Rn#W zt9|Q)=1nvJFf#+2jw0}LX{{wZz*81myfs!XfF(NzWmH)?X6{wng0X|k{BKhq*zz1G zO-uoD>%F#9>7R+f05<&<o2NX%3Mbzn*!{sTRy*5Lx`#v@b~(mq!rl3(v)MzvnGq(vJ3(_;-yHT zwO*3>E#E^ot6KUL70t|TjffJ{8c}GMsK92TKQ`qBPecX=sm%K{pRA6@89SZ5Gv?=GkOSnr!B2G(L91#uns*_l-M;e2Kyn zrFKINrWGP_2Et?UL`qtDZ(6gfK2eG+7OVq04n8(1sGcw(s0DDXKZ&3Ga_dt2EfedT zA}pFWfyY$^{YOqXA5EO?;9JSMw=%>KffX%oDw|+UuH01O$V>z!-adSY*&3t?9vIF!bBti_QLg7P%%p8%sLO6#z%&cX9RX4iY)^tDWcgAR zz0dnMMARxM>E5mb9f^pJAS{2yKqBH|%R-k~$!ujo^6MlcPlzE7vR)Z-nyo4vSbty} z1i9D9y4Tim!Oc#U^qtjMeR^EwBNL} zfCII-ct=k{{FXVD)y3cv40J%>f-xJXYnMoMvI|GD$5_g}R{*VH1%p-+8_IR@Ol to1csIeX@@97iKIf0I+;C!}jn$0r1bLBXrLQF*bPsFhrW_HR$4E{sDH-q6h#0 diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive_large.png b/presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive_large.png deleted file mode 100644 index 204d6571e71457e13fdaf85d3aaa4f0366821d2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12882 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

M<7yFccRvA=V=AZrf7QZ=~ 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

d{SDnnMyr2llOjDp%AeIxEMeJo=y52Be8;M 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 JPDHLkV1nbGkH7!` literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xhdpi/google_drive.png b/presentation/src/main/res/drawable-xhdpi/google_drive.png new file mode 100644 index 0000000000000000000000000000000000000000..c18ae762ba5f1f6debadd114d012b8d23fd522f6 GIT binary patch literal 2109 zcmV-D2*US?P)RGsmP;zO zLe&==bfEGFXjrSyk4xn{qSy7U(YKn7MQh>uyT6!lK$p{LCtoRv261sX^wjtbm<+s( z|89;An^?Ezxi^(=%);Z|6QVP&ma(Adw!3Nh_8-XF8c5dco{($AE`uSVK|)nd>vjaZ*$b`arpqK zH+F*d==eW2R-8&XHLpB!2*|HA66}j3V3|Awx~qMuI1<5etpsR{iWpbi`pQGEvIh$S z_3@$e%J8+42(ZU39yzrh4;%uc+?sq~ga0FF%^IJ_Mp09h#j=kRo9)UX2+&Y061Vtr zei&%ZzitYS)EM&KxB$IA%?@BM9D$@KW4YH$u%d8?Ee`_Sf4GB#erpmSX$`>DB->|- zOT17x^$#D#J+#FOXR{MhDFT(4JQTjD^5f&^+asxi&OhVdH`DiWIMOIs@_tD;*cF9= zmm;{b?~+faNw|7%?!D$1(KEpXoqNmDp&|xeDhnTs-~?2PDMG=mJY|ON+koW*^#sPp zVSF4(y{-#7-WmJH#-C5__@_;27zZSc3LgHxgCZ)4(NAB^swxW30MLu8F*0gQajQSe zd6hjh5XeQKg0fYH-AVX&-V{bZy_dGk8L0E&!6-PA%C{d{(tk51xPqoii=`*xug)bn z(kS5J?=4owxHBV0_wDL~LU4SbC)anfV`BnzJ(IECrZgN7_&6IQvQvat1HdF6ux{(L z{#u`(O~GN@2Xkec-BaOW&L5k$;QiR2e^(L;*l?sUa7h^asVv^$Mf6@=vkvM7;s}tW z-tf&+0r87$4u>NVC_Q{M9A`!uU2CC5UyXOO2{=+yl#5BYwP*KRvcFT#K@diOLcz*V z-Rgg2zrawYx7u&Y>6=n=lyB+GWK9WAQyoO0^2A;wIQD$7W^>ot&vS1sO72O2&lgtqq&fU*mDtC>#NDA52s9JlerB&AuY~r&)-?2vR7vcnd61#?E3) zoZfF%dP1GiI8uZE=#LHjBAa7XycvdoJSYW?+smaP=m(C+B_529BWXe;%##K$aME0S zFe8wQK!st8f_qmSjk7y=?1QPNztl`oYx^df(*4@)b9JiqK{{1H;Nvc@CDr?GB(2%- zxVlw?^&aWFv|fb!6IYyST4x!{_r2CseH;C=8utUUApy#Mxa^bYa! z8jRpQw`ai!8SDJu zbW4PAjRM6FVo7Eph~jI(C8-f4$*mwsZ3EZLE*^jaGkR+ThwXa+E~yCw8C@X3 zpFe5V4Uq1>39i|Zk|ew5o@3Ir$;?>H%)G$EGY~gjz6qA3mx5D5J&2O7 z;Ljd+unfns4ad3exs6-AO92Ujk@h_VQJU6=8LL@P81<_e^1y!EBbng1troYZ0q3Mv z++G{|`~jE24R(Tf7Ypph4YI&n*lmzz-vL(yy5SaSC_0QwoOMg%l%k!)jOAFq7Zm!( z#ItyN)`4TZ29e)`^Nv=8{(!gYH4r7YA(*Y5MdN0#527V{v=o0HrF(inLMxk}czC<- nfKzhEBE~%CF^_r7GY<2AL%5^%7*9nH00000NkvXXu0mjfD_!kg literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive.png b/presentation/src/main/res/drawable-xhdpi/google_drive_vault.png similarity index 100% rename from presentation/src/main/res/drawable-xhdpi/cloud_type_google_drive.png rename to presentation/src/main/res/drawable-xhdpi/google_drive_vault.png diff --git a/presentation/src/main/res/drawable-xhdpi/google_drive_vault_selected.png b/presentation/src/main/res/drawable-xhdpi/google_drive_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..2166ab4f7bccf9c34c489b5840ee2e9eda7c8d5d GIT binary patch literal 1000 zcmV>P)I8sZ)(rS5SgUMQopt0zYrx;Y z3%iFS$jfiK0#*ioB6pKt=M zrxTrt^|Ar`XuN25U`TRa!}a=<74Rz%qA}n{;3XNbD#%p19(n(|w17)Agt`vS3BONc zjN)ny*I!anz#lZsEptH=@E=H{zr}%{Bm;g6A~noie<&{CLKR`+q@Xun`1@AIC{EPi zV?p77-XKa*GzZiyGZ7D{@f)l`4xc&`N6pPI#7NM4k;Zh`)F7t%r z=P^cczM{7l7LjR+BFiL*BCQJrXOSJ6DFy7jxmZo7+(FkZxfFg6Xtae(5Jb>eWULn8Lk1y zc7T9exHoQR$p@fM>G;dQ&`f9URE;y)ej`y}CeyLmptcpB9>3;Jwz^NJry08`J{-fD}fR;-5ODZjt!eU>GmCEs9K71kiI&r{$vJXIaIAORQoF}}WgO^Se?iG1v zZ$b!kf^b3P)g`x)M3P6B-3U~*d@EP^X1wBC@Bz-Xos!SzV2KSk#4Z6}*lSDkA`qiH z7-=rjx}{kGZ_o!E2MG$H5=f&DX&S0n`(p)rgpU;@3!Us(kiWYiIjEWvAU3DPwgLc~ WxwJAV{ei~-0000cB49~+n_dIgW9&v{$IG})7*R~`Hkm(_l&24 z#bU8oEEbE!lH~OCmW;qV3{1dY2I2!o1-a;W2=`Dm#&T7055j~X7p{bh5EjN*a1n%y zs1)?#J@6mz(Kv>3jqx7;(Ic!OnGiO|P;fJZ%rJ*^zz_UEdW7ZD@drQ9A?#txARH@Z zL({MwC*k2egztEPyEus*m|oNa=Fft#EQ&|7E?z(wn&a~B*Z>bIqes|)?m5;S0^tSf zM)i`J0^ttk;06@1o0x+;5T-`?hOds7P()s$dQ{~);|3J58|WPNuZXqy2gT$c)}d+` zU$Q5lggAl5L6u8`2PKS$wDOkgh$m1&JweB^mTQfNP{KV#o6?r6g{x4CxQbe(DYzF( zG5bqeZWNTFMi*DE9-cwP*p6msgB?&=gfQnh>J?RRB~*;_sF>qE6>$zKgD~c-Eb3G9 zJ5-G2Mf`;-u@L8cN9%$Lu7*nBTma8t*mG9rEmspCpc2}Krao+k${@@+A5b%A!AX!~ zCgm)65OU1HoG;JskYm21reClx)k;-M!2>ajVGF5ZQHhO*8bNT^f`SRw=+q870q<_ zcYW)Wd{+CpYcY5oSr~;G*n-2*s}5reW?&Su@H%23F1h-k1pI<39D-Upgev@kgm4IW z4+^ml>exOM;+{|hOhyTgLIdn5N{}3^fL~z~G{`pNt3U=!L?38~^+lrVfDd5-G)yeO zL#_fojg8PSvk_1G8}Lc&hK8x#c+xiDqu2xubDQy~X}|<5fO^CNB$x#32lbeKz6SgT z>QUdc6fh0jp&qpz=}iViDb(Xin+kZ3<+Ei+e2NQDM=s!fbhCVRy{EB&MNkWaAQ%UA zWF!Pbp%w~n4;YWVPz&erDjsq4c5w&~;!Qt}ioJ-3xVm!AL=ZgAYhm}yA#ccFNNBo3O@DYAOd-TFooQ7g4V$l}rr^;&q6C52{JCTle@GmkTKFYwq zcoUh}K`8G+9hiqFAbxrlOQ82ku@FZR(Zj*5Crc=Up_Y!}8%Tis zhqF*igM~5$YT-2AhXmN~I0v;bMJSu07XF3=Nhhd<&2p{dY-lJX$ZDX5j)=uSZABWS z5lwg|AaIm8;WhnfEdcaHYlI4zVTP#>*DG^7#r(5qgA z7lsIJZzQ+w%OOn#Opolv7vNi{M}6n(0_bq4#|$@F;Z4Ru zs7EY9vds{}7HF8;ibo@LjARdnPf zV?Wff{U~-j(rhmt-=GSIp_UG#3g6)3*r?$)T5l^@gtV>T3)1-NJ0QLbm0JWIsyC;_ awgLcBGc}jhtLiNP0000<*cm9|+_Z^c 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 literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xhdpi/pcloud_vault.png b/presentation/src/main/res/drawable-xhdpi/pcloud_vault.png new file mode 100644 index 0000000000000000000000000000000000000000..13d91ac500a789c10f2fd6e8289d356f2921eb1c GIT binary patch literal 1923 zcmb7^XH*kd7RRHAsFYE_p{*hevaBGejIqt^GKe58KnMX6AV#SxDm^Gfii%4UFf>_V z=pel%5J&=q7KaiDB@jYLp(lhAK%{Q=%O20VU-q1L-+TA>%DL}!^`ENTRA?|;7>eT}PjxN|*LGGE?SbX2C>pcOx^c)?hKdy7Zk zLLZ*p9NQZQ#W{A(cD3=^OD8^Y);K+L6i}T_@p$p4iBDo>;`xH540?Cy2TX;Zm(Rte=E0@%p=GA@pe`;n?=hiq!&JDMy60xGHD;GC#&rn z?9n8&fw=U+L12(QKx88osKD9Z-#tFCcIu5ad9PwRtNR33MN6V`_SK&RQ9$kGML9M< zZGZn@KhH7s&M*0UIjflUgT2?gvR|VQ6yMs~?NSGMOF9SA8#}_Opc}3=B^dyK#J0J~ zZ;(*8Y`O2PEf!t$rRjj1Dj4f_I$lL^VEcBYH&pxid=AnNZIao3gX{ zK%a;$3x(5a^H=qxoeE#|FFTNn%eFANaK$SP(iTR`IzP%?0zuYfRHGAbDG})L8n6{v zuSEZu?j6o%7)iNq@2J6Ew_J#VDG#F=bGhs7kb=Oq(_~KhHAW^dQyIyBD4z+{`Uw45 zma1GiynEH+_MZb3Uow!HWUeX)d|bU)=H`uJeI1tIe+Y(y8!k+9p~=YJ^#w<{^+L7o}?C|Uco>$MWyu)(m)}0m--dd{j$n3 zsa*jj)S2ELO}EmiJq)~xL0?V^ipqu~3q8zU@}D3g>MQFt_w5s=8K2SVQfkIAQxeq$ zb#o;cr`wt9rLhDkJT&$6y&-rKDTVpg*d+@?-4CatZb-&=#ayDaW%s;BIXm2g zv{?YuyyN$~B=ADm%8^Kdl%3_p3Jo`rpGESoOyUt}JduV54@C*Co<#ignAXBpS?+ zHm;4O`5-B$gjV$B>~S!TneC9uj~2(D>D5&f4+sX zHf*})IoMn`__et%xm?q<>4NKW=tWEQrxz!69Iw5#WN*~vxl9D%kKMjRQMf)di?xRv z<;qpnB7!CvN|E}j42~mzA+#-(5Mo`WaeXuy-VB^L)@c@|Gq4^Uzuu#LsB;71^~a6~ q8RE1Z>=MzDh^&9se2aEwTd7jbxeNFn@=W~00L<@!Osb3@ME?sM_W)r4 literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xhdpi/pcloud_vault_selected.png b/presentation/src/main/res/drawable-xhdpi/pcloud_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..593e456cb628f806a647504ecc43957d87c42315 GIT binary patch literal 1143 zcmV--1c>{IP)Zg4eH5rDp@OC-8F_hGO8t!KDh=$ySFkGgHM&9rvv>oR5^o?I5_mo=WzP2vToaa3 zYnBTfgzsS~^*sic3OpW`a>vI7Hey6CJff#!MRcOx>BNdS72m-UGy;wP6?hCRA-`fT z&m~U=d*WAELXIvD%p~{+9!D$WOpE<+3!cG~xCT3+1#&v@e1Z>f21@$Qc{ef;x?I#G zypEHxGxo=M_!MJt76wDe;;y9bsL{aN5*lm^5SGSoco=g)F_yzy_yjXR$m4Zb&(P!o zvkBZ>#$z@JvttwvL5=5j$JO{6%@AhA_yle?S#enmR)IfoI+n)95C-B)oC-zcF)Shr zMbzN|JdPR&Yhw_Wz|F7XZ?GK{@fIw`ILr$<=ivd!nH7q-2^QcIxm&k+x*00c0by25 zL<(|S0hMtezC;dko`vB_sDx=S z4y!;hLg&t3g$xr*d+vUJic278PMn3=pcolEjRzqQLqmp%OA@$?M$L`CaH{9adLZt` z7ifb#WMLjI37M*mL_6eRLHvl9uvCeI(hcz$-o;dqr&&U#YKsLfL24imEjS)hj2eG#QGQn1!~7{0n4R|7QDBk9qMCU zcq??lTj`d?C|ro;(FpaDddz|&@Moo478(2oOM~!DC*W9EKXGjM#&3FzKvRF-x<3($ z3mn|P4_|<_U@5h>?*Zr)u#~w1@(nEDO;}32g+e7CLih$&<^J7ckXwiAGLlyyHxB28 zKpLYbI>9rshY8UrFEop8B;I==A^q002ov JPDHLkV1j9v^cw&G literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xhdpi/storage_type_local_large.png b/presentation/src/main/res/drawable-xhdpi/storage_type_local_large.png deleted file mode 100644 index c08854054dd73531ae5a7e99be485f1a4664e6c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4034 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 literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xhdpi/cloud_type_webdav.png b/presentation/src/main/res/drawable-xhdpi/webdav_vault.png similarity index 100% rename from presentation/src/main/res/drawable-xhdpi/cloud_type_webdav.png rename to presentation/src/main/res/drawable-xhdpi/webdav_vault.png diff --git a/presentation/src/main/res/drawable-xhdpi/webdav_vault_selected.png b/presentation/src/main/res/drawable-xhdpi/webdav_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..85abf3300fdf3ea880870e066f552529b905e964 GIT binary patch literal 1337 zcmV-91;+Y`P)x3$9ZUg&SN}s!WSO`K8(gV z4Gpo=XpD#BB4BDX!zE~#T|zUYj;(-Yr|p+U+RQ^0gM0u6(;sEeUE3H7rPe<43^Lj!aK>74>Lhlbb<^u#=< zgS#kzI?y0Aw+r}i7<$W7Xpf6f7cS#PL=z2I7JbWpN&g)p*HJ6pC`j#}{KY)Ikos zWa$7kWb_X>&O;>>kKV#*Tt#w-pR~A+K@dNS&^d;ke+r0Y71S^eV%^fNi%Tbn;R%#L zA%_mXD7HgjnvD}k3b9N>Ca8@u5X)n@V^eH=VlBApQg)j^UU3@b@H@odLk$Q_WuW*R zVwr`f9I9zSh@}|R#@AwTU6wqZM3K@kb@Xp2QGn<4lcYFG)eq{3CGjXq-C;_AXsh#>>+pbD-85wJNF z&q1)sAwVtTd`KNE5f5EMX^7!h97SEIgRgCRDO z;uLa141;hTY42HtfF9<(9uv0#XWIO++mPMip=|mPirx@ICMYu76d}dm5KA*RI~j4= z>DPxC-o_|2FiQBd5F0gYE3(nm7+T^gQkkJ+aK>$s`N?L1uZh7>%N&S(UV^%C$jvfw3#n}u_^d{i`y1lmTl`{q6k;fAb~Tn{#`CrU)P*H(7W%ag3;cIj z48>ANfS$w$cn*pd5C`+2F7$EJs&#T$;NQhkT)`Uw1{;Z+=wfy?HZtHQ)P<^Un(gd1 z3%ueZ40G|&d&4`C|Q zum@!kflm<)4WpJ2yMXC&1nSWdybHk_$c8`g3Iu7;1h=4Jr6Dz%$KpV2bEJu@k>;j20}Zh=Xc|`|&8C#di1D}p4bTOQM@FQC1S{PC vRxkuB5QWQ7+{9Vz#|jKVHGGa_kWl&`J5IX~e46_t00000NkvXXu0mjfV5C={ literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox_large.png b/presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox_large.png deleted file mode 100644 index a7f144f13d7d45290d6a88dd437f600e1c7d71fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5266 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 diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive_large.png b/presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive_large.png deleted file mode 100644 index db50a85f8cb29d88ca95a196ec3e1d0bcf790875..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20926 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 EA0n+PasU7T diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_onedrive_large.png b/presentation/src/main/res/drawable-xxhdpi/cloud_type_onedrive_large.png deleted file mode 100644 index 43d8b8aa28f35e59da2e4ec10ac6367d2d96fc2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5680 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 diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav_large.png b/presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav_large.png deleted file mode 100644 index 5ec3ef44dd9c4fb46569ec4e9ca1ee5e049552f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53102 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 diff --git a/presentation/src/main/res/drawable-xxhdpi/dropbox.png b/presentation/src/main/res/drawable-xxhdpi/dropbox.png new file mode 100644 index 0000000000000000000000000000000000000000..34f3a49687c3ea90e4a8e06092a0a587340f082e GIT binary patch literal 1508 zcmViHIPkS$aMN};K63L(zXheg7V@j#EbhVV`7j| zFciTMFP0;TrGZjF9L$9HX(f^x9A-XTgZSYE8Y0N_K)GloJcI=3B^o10=OE)^7DR*H z_}P>|xzJZw2hqtM{GwBsRqzO+gOBKj@P-7+4TnJsyoTuPBYGfwfI)tZ4G;sIK_*=T z$~We~ zMTkZ2pt#DN+#kUZi!4M!p919zV_^#15F1#6ME|TU7cN6=;2CPer5Y&z84*VzHggS? z5dyK9Zr&cDF)_hLkI)53@Xy+^<08Zco}va^zP^TL#RZ5(ZlQ>Wx+0oiqOcyHLx015h`~-HqaU9`gU}4GO}LXsBC4Jvr64rL zD~QfMpsR+F(wA5ZF~BLLH*zeW467kJ*@Yi<8p~J2Lx>LEqYJ{BHQQ7k4r?s#p+Ybd2#b^|u~h*&Oo)Y$035;}rUuFdzr$9DMu(AH#~@v(jb{)Ig3&LK&cQ%G4!wiuBpCe>Nn@Bl zU^_%d7m>rLMu#*w0WrvBLhS)xanHDD@Cb*As zS{ohm;~Kxzou_zW@GDkSwQP`)7^Wk)&@&O1KyB>|nE|)ArDy)FB<@0V^cJlV*67KR z;xMw-T2AF_r$<>xFH&C!Z&74-$lnbglOqt)6iD)L2Z`?|CR;kHQdS7 ztejxZf^!fb-9RCg-l^sRG=7~yMwQONo_=-?&OtmoY)uT09$tzM!3acE*=2H2&lX5U z=iojF)LcgaJ1)}wf;|w0lSpU6E|c3Rs`BV>;-Pc!GlnCoof{p3&=@cA9-WmPY|8d- zvvz6lZ%MHVf|E#V`*nFm@js~a@K1)@)Lj~ic6Y> z*F&FEkaNPVwqOfl7Lqwf1{=-^)Ctt#1nLCp1nO`Cbpmw)b^I01tnt*E=cGme0000< KMNUMnLSTZzthg2c literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox.png b/presentation/src/main/res/drawable-xxhdpi/dropbox_vault.png similarity index 100% rename from presentation/src/main/res/drawable-xxhdpi/cloud_type_dropbox.png rename to presentation/src/main/res/drawable-xxhdpi/dropbox_vault.png diff --git a/presentation/src/main/res/drawable-xxhdpi/dropbox_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/dropbox_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..92939cce594cf9096baec7844d048dd1e7f80817 GIT binary patch literal 1385 zcmV-v1(y1WP)*v%hbs1Ej_Hq6ZF!s%xMeL!L6bYW&@ zX6EI9&ifWY&D)7BA6;8s7az61ncu>_q9Yu@RlLAQ2#W9qcW@pD;9v%NqZv%HB`dfA z`eO<1LzLXdQuIfIbT70z#^Er&LIUREFvg*JIu-gqEJZ#fN%FB2{|j@W9k2r~Bt^K` zi4I{Zv^9Ok0Pc9xP|e=4*sT!?}LXaf_2HaK8AOM#2FD7H`slAzZZf(T3;A{dO<_OlY= z6jx|<l(b{+3KzA5L-EqTymM`P}snEkd7lOGc2NQsD zn1dqASrVti-a;F~g~Vwjj0GbsXGxq~H2Ag9VGti&EJ00}0Mx`1xR$fThhe`Kx)G9q zR~Uc@jDiRT;HCX6-!}eQ=rf3dJ;;JdKo<7c&QkE~d!a2MiV86sWnkh^2D4FMJ4;bZ z!=X1M0N2q4CI+2x-FlY3-iE_WNI+aHMJ*V3)xuJ^_Ok*pQ{{Bi$D$R%0K5u$mX~8J zKLxTEU9s18mL>ZQhiecEiZB<|Fb74pvn;r7Jo~Sp>_;2#Wi;#$d6w5p^Zh-bfV{?F zL}0uS!63Z0ofVKA6X5FZpcYIFYQcePKTDSh;Qz}Fbc6AsD{k1&(qk0=1d1>RWnmIf z7IRQ!KTGFN{+`l8kPV|K8~bc#X)Q_@6Wl}(LIg&WXCZlIIm`dHDZ!u26hvSG6u}gm zS&44!9fdQJY?uIKVXxIJg;UR&ikIxl!YC+<*(kJ~m7tm0m+!8lD@+2q;5y`+98AJQ zW_%r}j(RXW>Xe(JPW~TpMwE9;B>56 z0UGB2K?nbCV3c>!!9+x1e2RJ(EpJx9Tr}|iD2|@3Xau9A5w<`q^E`?JjDuM65Itc6 z(i0CMmW)?;oYA*ys0I_TYPbgRLE&*mz=2qB8XaNOc@`=Q9F<4H6@0-0REAMf84K_Q zVu6cx#TN?ff>`tjy3mwD!ah0}M zhI~kp6u?2-bo%^H4UEHKxRyWvQzP9!P}LB_umR5?N}gc@hM{42K2W8vIr?BG92~$k ryhI-4n>@V4H5`P4S?Gi2Fv<2e@U7{xoAck>00000NkvXXu0mjfrfX|g literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxhdpi/google_drive.png b/presentation/src/main/res/drawable-xxhdpi/google_drive.png new file mode 100644 index 0000000000000000000000000000000000000000..289e2fa6509a7e36cff72ab0fbe28502a64e61ed GIT binary patch literal 3394 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 Y3tVWvK+8E{{Qv*}07*qoM6N<$f_|-TZU6uP literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive.png b/presentation/src/main/res/drawable-xxhdpi/google_drive_vault.png similarity index 100% rename from presentation/src/main/res/drawable-xxhdpi/cloud_type_google_drive.png rename to presentation/src/main/res/drawable-xxhdpi/google_drive_vault.png diff --git a/presentation/src/main/res/drawable-xxhdpi/google_drive_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/google_drive_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..395f49485ff8a4464a1b656e87f219c605168162 GIT binary patch literal 1388 zcmV-y1(W)TP)hj z`|*BRunG>v4JhFkbi@#hgSG4Pvze%m3$SRO0^JZVVO;&ED$qsnGbHDJ#v;B0T@NR9dovs(uG zD5S=y&@K7h3Bw{yZ zs5=JuUuI&H{|mGP@oXB_jCn;hgOq7!(3*G$;#mo?fi4=T)%yZmj4fgYsPhdPh7$s` zs27a{8f@M<2`j?Ih#R1WV{v7uw}hIPMgy%6@$5l3#ET!G9#;lBANoK%sviw>`2Zi_ zW3fbn0M3f}%-uj4>d^rPr{&;6&f9KcO)o)@XTUmoZnh)0)I5I}T*>WR6Pp*|1sWY9l>uI{_wk)S1;bbz`>8R(K24e?}k z$HSozkIqsA5FMb#87!o|9OB8Lj)xl|o^(}S?ZgdGuP6hZ6P+L)-01sq^Js%2N7I-o z$~JHc`$Ie^IghJAS{bx7&NXe9_^+z{>^#2w_lHwh8l$SLUDDZks&4-Vr;(s*Fd&2N zlA*3_ACRRT;1qUAcqy!Hmn>}jf*R*Mg!!exDBKZ}z~rp8OB@eVe6%(cu7MjHVq#yiYHcXo1<8SuMR8;dmQY@?Oo8OUjTYs**OaY^A0dAmiF;Di z!J>S(y1^pUWh)Hy_?X3GE+xwv>Qrnvs8?p}xMU@|ATP*=nJ~UJ3U+HH%#TZ@ptmHb!jHsaDF%8o#6T&;lJN0ZHd=+x*d%_ukAk|--DodHtMD0`#J}Kw(}MlB zn(5duQiadUBtE*WCfLTWZ}15uLzDQgAqHMoY)vdEwk9T+mliIt+umJhirLdVfS@>l zAmSv5QgK8^#A8L6WMF*E(5H|Z^*LtG@IhF`L0H96ZP`CeusBSxIPTK-fviTOe{mpd z#EBT6lS%uDSvk~tEi|d}%gME}czWvW)dA~u`@;C$UVUY2u3h&_zEMH4^{EAu?hv7z) u@C!O&XryC00YlLVzoLYja2QsFOWW^h$QY_zbzyb@0000n`;OA+^26CA6V!&^Zk*tHDQ;_ki7Xzjt;}Y!wj@gb&$e8wGL=-2G zu@K;(3y^Vws8>VAA>$rB0gTy$d&n5~de9bRtOqdYI%I75VNf3m5G)J_DCRIM2nF={ zW!MsA?4v0FF`KXt8Q=S9TqkZI~!u`)f#u=i1 zeHcwZMjqW|pC7>wxPxl!4nJa~Y(qElkTK!+zfBp)SVJ5soI)NsMG6V5K}IGBFGc!M zgkT{L`9vNTgd+Nb5HyY>$mf1X!tbx^lDL8b;tEML`7P!<*nt9S2k-hN=BM}<3bd9W#MLs6iYeQLC6SWT6I=MXVN2 zK$}p5+C;?7pvzE$TXrkvm(c%0NjI(+$@dB**1dGuBjG!5A>_RM)>kf+?=mVRx_Ay`nA&R;n9 z9CD5K+bj&dCg?6?L-vJpvyiL2-?sZw%nu=(@`zV>L**WFo%h>5suXk$a$xnDOCzh0 zbH3d6({nMqP=Fjt;{&vs+ce}p-)t+O>#3l#kS(FFo(g&d*%CS`kNFc2^4b0 zpQWJxwGRgt001BW!218&2!QH{5H^;(Jz4Pp0000005H7xgu;`DV4(m2002ovPDHLk FV1kUlcuoKS literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxhdpi/storage_type_local.png b/presentation/src/main/res/drawable-xxhdpi/local_fs_vault.png similarity index 100% rename from presentation/src/main/res/drawable-xxhdpi/storage_type_local.png rename to presentation/src/main/res/drawable-xxhdpi/local_fs_vault.png diff --git a/presentation/src/main/res/drawable-xxhdpi/local_fs_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/local_fs_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea7d56351b17e56498f2b5625e47a9701591ecd GIT binary patch literal 1259 zcmV0h5n zM8r8E7qz`O3HHQQcop@~1tT#BOQBeU1(<-r=z@B96<1+TNVa9`2k(sA@DnCO4VjFe zaT|6{d!YZrqv(zeP|E^z$D{aPngTrxKVu~{OIG4%9G1(09)k`Dpa~J6BaX?XKzB!D z^FbOFpfPq&a-fBH1}mUBVg;T-VbTKK7k!{Pst@){N}#u588pW&!>!p2v;dz%Q|2=i zxDK=wt)VH^8l|oRt&7gklq$^_Z~>TOknsDU1Bcyf3FKEh;ZmQ2Q{SZH{X`j{wzc7z)A5(FnAfI7AwM?&xh z)Sylk1$v}~Ke{tyqhFwoeE~t9g-P%LM{X_9dQc0lhhRI$H@ms03&9;w3+iPG^nVtY zdZ(i%1dl@5tL321LG6f-Q)-HA ztWZ+}T1Bjwn;DvYx&~s{HCPSB%m@{B`50_6(D{bmgbsjI;9Y1)ABbUnpds%5hk_%o#Bj>0&P<{~wYv+r=5J zW;Qx_0d|hR701i46$f|}>Q%{O=C(6>EAc;b8*aUN=C*`;0glQXD9{P&MbgP!S&bLq z7+?5hPD5Xsn4Qu@|6Cfuqc+RCeR;S01_ZtVLD=n3&p?Zekk5RWWMH_>P;Yj>Ionz7(uNJsg>)Q1KXqTb9g329&s;z_SRU8a{0RZwg VI}&yJg5dxF002ovPDHLkV1nUqKQI6Q literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxhdpi/onedrive.png b/presentation/src/main/res/drawable-xxhdpi/onedrive.png new file mode 100644 index 0000000000000000000000000000000000000000..52c04c9540a05873b9dee5d7e1b10a3e0d7f3c38 GIT binary patch literal 1266 zcmVqr#8cWU=aWS006+-vj-r%xiF05$$Gc8ZQHhO+qP}nwg;b8+qP|+@AsT` zQY@3(R4{wIC%&epT}U$G!JRE4(C8QMTyC<-aS0~C>n^oNNDZD0dj2h!m= zY=gm&1ypwr>I1c43y=veK~IS8Zb7}E30woRz-t%-QQRSDPB;O?!DDCvsw)Qdgpu$O zh_innwkrjV4jX}-=qBWJg`n}_G>`yZL3uj|jSrWA1o93l+A(M(I0_`7SCG?AK~?x2 zNMLs$zFmTL1+sySpb8UI>3zZn3PTTA4FACarRULmYWKsWb~`|7=e^|7eg}E9-Y9C+ zj!q4VU>;B!*1$_3y$~9K_lOqvDMaTU6hTy|38#R5mfH1zNMWaO4T=CMU?Y&(*d%zi z{6~aSDi)8REM_fu0c4q8$Jov~JL&+dgR+t->Yo8j7lrS8fTE5+ z&qA1OHzd>(bUcuq1fBukPTMHr&|}&zTImL)3ofV@(-$5A*^Nh^6BK#KQTBqdI$~hA zA#q4SE7~oj&!E#3YuZ&xx#A9`UVV>}FTG81W?thR^*Hzi7j(8=LWUo8o)XNv!6F7L z$>LiSbLthIQKtqM^o+<6J^P=+xpRq<;VDG4Fr`{`mqq6l^aEtnf@&Wm{v={ZzoBQ? zKVue%g)%I~@;kg{V8i!8Q;8H&?Q@bMjlDpL7Tz=(F!ULiX6X!kf<#)-!Xkw99&nms zOu5XSD&x__n1S9}(2~v!88G}D#hY`TQm--|Nw68%;0EP zJ>a<|x8tPClx*2;VZw2kfpXemzs0PO{=?2Xeme>>0|_w$F&vwL!dlP)K#o%Tez_eJ zFVtQ#_LpMC9f~{4@C;oJJ~2ZC$P3k>8FaR(Mo1gsZp*iKn=Ulh}i`NI@I?3uPgk=F~SXjw%?3Gmx5`!FW_b z@n{b;AL?KOe2^B)zy{PozGw>cYZ#A5kPdl-@%UOK2l^e%M+T%zWMBcl6G?%V#v~<3 zx5>aHly-BV@o0>PkP+e`8YA9qfmXy;$OyF+mE02O`?wDo;qK#oCj*VcK*%UF2yu1? znv7YHQEC>F?Fuw6)<8zNHOOmqp!u*HGK<)ad{zaT2U{Ssm@UYoJJ2}Hh0LPnBTi?a z10l1xfw}^%0=}RJRm}!k84n;mAPZCuH_$k2Q@)^wOb{1Npr1i1JV2dL11*Z%kO~iQ z8%0A2G|l0;%FSqvKcXaZKp+|A@fUQ)UPy;d9`H|4uN0 zE`YSzT$IqTcC{VSMhi0w^qV%nJD;K!gn=*PEi^`Re1d-lSh|Qud-!a8mwq!)pec|x zxr3iVC`zF_&O-)p08Nn#Lh)}rg|tD6QK0$o$i}stA1EOtp<5V_;W}y~M)`XG!V8Xp7;|ALa0znxn7#JTp1cx!V?=Br{eyB)`ryREUF+eg!$I$I18ykbLHy~fwWj{ z|3GIzYLbrJ5MjuM&Kg?i`%1sIoDE&68UBG@hSZ<~1XLC9A$nsNI^!Lq@i4xGb&w{E zRKDgaNDEzl6=(%W4SGN*zK&%9uFpO!!;c{pF{ptX;dW<~Pf9&Vi&c2lUy~-SK%5dr zXFP`t(iDStY(b=i*l6xU2NZ&MDHr~MR@ek-fqw~pXse-PTTTgcyoqVCj}Z%@_!W*o z8hC-}_$fpXzK-RP8nx9i-0TD4rv;=*oA7lAMIzecvGOAHLK%n<#o!}6htwcNn5+Wv zDbsD`H1_$PqbIUMC@NzrK0{6j17+}gRKwp<4g#vbX&U>l5&Uo%GE7S#{EUOt>@@xY z5u}1>8DR4H0IET~tki@w>4@NmYmi}T3h`1-JhpJP@lQc)%^4Cx!S5;*)I1k_P5A8t z3dc_%Uit^5X6Mj6$oirmV_&FcHp(UCt0srw_l3zPToU4?E}H9!-a>qUiR7{df8IRA zvh=PH08CD%2s0OBrG|2(hDK;AewaxqWmBBRbM!T6l*(aY2;IH{5HEdVHqaZ8VM>B{ zX|RdDXU|M_WK_lwJTSN{{2cigx zCzwkalwl5mFUY=hKC_L|K243%A48b0{3~Jul$+Pa{xD~m8W>!AzmGkTP>qHFV6xk^ z8Zt~xl(F*t05kPtGuawg44rWu(xjdEZh*a1>f`LMbpK3r?9B~5S3(46m@cCo3PTw9 z4$>@GrSxHt!Q5;a%!7QXhO%^RYUtV4uHOfEoJ#U_GS8F5^j#B_-_J2SeOEz4|M6=GnVQ zQ3o54Ve{>O@<;m(s!E_XrsFcCCYLcCwNWA~-SJCR4*y15q+l%$;~E|+HB;Ac80(ON fcKEkj3e-LaV#ABVBC&6f00000NkvXXu0mjf6Oj{6 literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxhdpi/pcloud.png b/presentation/src/main/res/drawable-xxhdpi/pcloud.png new file mode 100644 index 0000000000000000000000000000000000000000..007cc3270c65c7d195c31ddfc45fecd42de0840f GIT binary patch literal 1623 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 O00003(`-r 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 diff --git a/presentation/src/main/res/drawable-xxhdpi/webdav.png b/presentation/src/main/res/drawable-xxhdpi/webdav.png new file mode 100644 index 0000000000000000000000000000000000000000..c913817afc86816cf469572bf3fc192664681fb8 GIT binary patch literal 5538 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 literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav.png b/presentation/src/main/res/drawable-xxhdpi/webdav_vault.png similarity index 100% rename from presentation/src/main/res/drawable-xxhdpi/cloud_type_webdav.png rename to presentation/src/main/res/drawable-xxhdpi/webdav_vault.png diff --git a/presentation/src/main/res/drawable-xxhdpi/webdav_vault_selected.png b/presentation/src/main/res/drawable-xxhdpi/webdav_vault_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..4661a93d1440c30e1fff7fee90ab09585a252bc4 GIT binary patch literal 2040 zcmVlwt!~vUr0UmU;HU80X-T^A@$r+9Gzr9 zo8WFpk+}y=>N=n;F&k2(W}{_Y1+*h7AVsbM9pesYC%g%1Al^i$xB}V%|A90x|3QbS z1KI@hAPv+4G>I~xcS9PuyQ2!|F_4n~J2q@U2VfZlCBy;)f(En+UV)^@$G95XqZ<^u zVl*zrCyU`I%dR_Gn6DD%#qP$6uCa)@Ck z40rK31dA}Dz|Wt7SMUEaD%A>a)(32q{oaPar z_UPnj3}s;<1c!M1x|tBX?(+8v#^4=@h1;MEw?RCdtOxY(kPx#wt%3c z_F%CoS#=jeER`vzn50f=ejv0_z90z5nhFCg4 zIju1XVSA`ryavHkoC(n!2^9hTw%i}eaS(niFy0zm@0jjBtGCO;mYZvhE5g(oYq1`K z@Ka0y)gK)C*nA4!a~W?<|A8_b=eG>i#-Bj`3uTdb{{`bAVFK!r8Qi9|{~AHzQj}ZX z!k0rC=0JR?P);H%0o~~lfJ-3Q)+G(Mn})uej>qS?PdQSm**FU1$DV`_*T zjUUj&Wd?FKl%;VH$6zveBCp{h?BSBX_JFcf32}VsqiSjeEuUI)my| zplTWk<-!bh2dshwbh*>Mi?AUAmRg;xNAb~mUyRwKIA{pb? zJqbZBhC>0y;xmZjTfO(`T{T6%NUltu0*R_2a4_?>8>I|~Lp)r7O>sm7Ra0+BaOP4RV0`$7h^OW_LmT`* zfzsH{PTmEDiO57yH8sYskbouxUCy{AQ~*72bAcY?4FR6E526uv#5$ZCQPng%{Bp+l z7u@F13~eJ?=6xF)VIUSkQeaA5FA3ilVdwwX;4CO?jvOQf@@9VRzUnPHHCm$nUckLd zqo6PdKSNStVN%;__S4GJAAIbqyc#W_up1UZQX-GxNp1Z40o}Bm_i2cWFK{KgYfs<{ zunv+U)05ts_#dNs+5!`BJub&F*bGWjhH6Oi!fZ_h#zP|Zq2+;oy>SOtK~iXZnznbZ z$8_wj&9S$~V4Q^)u@;h2ub@fk0D^=MAkZBPDG7f>hVC9n19wl<#|+JfG*DSIN%28g zrGv0aN42H@Fu~Gcf~Dgw<3Er!s;7VHKvvx`SXLxS`!Q)b)OtWYKh*ja3`pa#>*rww zq@GxT^U`?ix~}wMfS8Ox7LqaxF`_=dTxAHJN_Z$(i76OTOt1gxjPa;JzK&o2)4AAR zP}K(~<8Sy85|JPAH=K+<4e5 - + android:layout_height="72dp" + android:background="?android:attr/selectableItemBackground"> - + + + + + + + + + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_alignParentBottom="true" + android:layout_marginStart="16dp" + android:layout_toEndOf="@+id/cloudImage" + android:background="@color/list_divider" /> - + From a94bddd96db168db15801444a3493f68fe05ec3e Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 25 Mar 2021 16:26:59 +0100 Subject: [PATCH 18/93] Enhance usage of cloud, vault and selected vault asset --- .../presentation/ui/adapter/CloudConnectionListAdapter.kt | 2 +- .../presentation/ui/adapter/SharedLocationsAdapter.kt | 3 ++- .../org/cryptomator/presentation/ui/adapter/VaultsAdapter.kt | 5 ++++- .../ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt | 2 +- .../presentation/ui/bottomsheet/SettingsVaultBottomSheet.kt | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt index ac5c6be7..1ddda74c 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt @@ -48,7 +48,7 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter throw IllegalStateException("Cloud model is not binded in the view") } - iv_cloud_image.setImageResource(cloudModel.cloudType().vaultImageResource) + iv_cloud_image.setImageResource(cloudModel.cloudType().cloudImageResource) change_cloud.setOnClickListener { callback?.onChangeCloudClicked(cloudModel) dismiss() diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/SettingsVaultBottomSheet.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/SettingsVaultBottomSheet.kt index 84f4c96a..163d759a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/SettingsVaultBottomSheet.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/SettingsVaultBottomSheet.kt @@ -31,7 +31,7 @@ class SettingsVaultBottomSheet : BaseBottomSheet Date: Thu, 25 Mar 2021 17:28:51 +0100 Subject: [PATCH 19/93] Update to latest version of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 466772f0..852599ce 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 466772f0538953074cfafe11e9fa5c3bc8e84a5e +Subproject commit 852599ced5a7cabf9a9afd10d39a809d16e9b069 From 8ad6ec0df104f0e6dbb3b833d7df971622ad123e Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 26 Mar 2021 16:41:36 +0100 Subject: [PATCH 20/93] Update to latest version of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 852599ce..0c550f2f 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 852599ced5a7cabf9a9afd10d39a809d16e9b069 +Subproject commit 0c550f2fe05336f4e49202b500b784eb4cbe2712 From f14c46fa2a874ffe382dacb2472bfc75d4d35df7 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 12 Mar 2021 15:59:49 +0100 Subject: [PATCH 21/93] feat(Data): add PCloudNode, PCloudFile, PCloudFolder and RootPCloudFolder --- .../data/cloud/pcloud/PCloudFile.java | 62 +++++++++++++++++++ .../data/cloud/pcloud/PCloudFolder.java | 49 +++++++++++++++ .../data/cloud/pcloud/PCloudNode.java | 9 +++ .../data/cloud/pcloud/RootPCloudFolder.java | 25 ++++++++ 4 files changed, 145 insertions(+) create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java new file mode 100644 index 00000000..efca898f --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java @@ -0,0 +1,62 @@ +package org.cryptomator.data.cloud.pcloud; + +import org.cryptomator.domain.Cloud; +import org.cryptomator.domain.CloudFile; +import org.cryptomator.util.Optional; + +import java.util.Date; + +class PCloudFile implements CloudFile, PCloudNode { + + private final PCloudFolder parent; + private final Long fileid; + private final String name; + private final String path; + private final Optional size; + private final Optional modified; + + public PCloudFile(PCloudFolder parent, Long fileid, String name, String path, Optional size, Optional modified) { + this.parent = parent; + this.fileid = fileid; + this.name = name; + this.path = path; + this.size = size; + this.modified = modified; + } + + @Override + public Cloud getCloud() { + return parent.getCloud(); + } + + @Override + public Long getId() { + return fileid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getPath() { + return path; + } + + @Override + public PCloudFolder getParent() { + return parent; + } + + @Override + public Optional getSize() { + return size; + } + + @Override + public Optional getModified() { + return modified; + } + +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java new file mode 100644 index 00000000..fc57bb0d --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java @@ -0,0 +1,49 @@ +package org.cryptomator.data.cloud.pcloud; + +import org.cryptomator.domain.Cloud; +import org.cryptomator.domain.CloudFolder; + +class PCloudFolder implements CloudFolder, PCloudNode { + + private final PCloudFolder parent; + private final Long folderid; + private final String name; + private final String path; + + public PCloudFolder(PCloudFolder parent, Long folderid, String name, String path) { + this.parent = parent; + this.folderid = folderid; + this.name = name; + this.path = path; + } + + @Override + public Cloud getCloud() { + return parent.getCloud(); + } + + @Override + public Long getId() { + return folderid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getPath() { + return path; + } + + @Override + public PCloudFolder getParent() { + return parent; + } + + @Override + public PCloudFolder withCloud(Cloud cloud) { + return new PCloudFolder(parent.withCloud(cloud), folderid, name, path); + } +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java new file mode 100644 index 00000000..c9b03334 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java @@ -0,0 +1,9 @@ +package org.cryptomator.data.cloud.pcloud; + +import org.cryptomator.domain.CloudNode; + +interface PCloudNode extends CloudNode { + + Long getId(); + +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java new file mode 100644 index 00000000..60fc42e2 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java @@ -0,0 +1,25 @@ +package org.cryptomator.data.cloud.pcloud; + +import org.cryptomator.domain.Cloud; +import org.cryptomator.domain.PCloudCloud; + +class RootPCloudFolder extends PCloudFolder { + + private final PCloudCloud cloud; + private static final long rootFolderId = 0L; + + public RootPCloudFolder(PCloudCloud cloud) { + super(null, rootFolderId, "", ""); + this.cloud = cloud; + } + + @Override + public PCloudCloud getCloud() { + return cloud; + } + + @Override + public PCloudFolder withCloud(Cloud cloud) { + return new RootPCloudFolder((PCloudCloud) cloud); + } +} From fdf144c65543f4f7a8b74efadbb4c787fd238425 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 12 Mar 2021 16:00:48 +0100 Subject: [PATCH 22/93] feat(Domain): add PCloudCloud --- .../org/cryptomator/domain/PCloudCloud.java | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 domain/src/main/java/org/cryptomator/domain/PCloudCloud.java diff --git a/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java b/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java new file mode 100644 index 00000000..2bc990e4 --- /dev/null +++ b/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java @@ -0,0 +1,135 @@ +package org.cryptomator.domain; + +import org.jetbrains.annotations.NotNull; + +public class PCloudCloud implements Cloud { + + private final Long id; + private final String accessToken; + private final String url; + private final String username; + + private PCloudCloud(Builder builder) { + this.id = builder.id; + this.accessToken = builder.accessToken; + this.url = builder.url; + this.username = builder.username; + } + + public static Builder aPCloudCloud() { + return new Builder(); + } + + public static Builder aCopyOf(PCloudCloud pCloudCloud) { + return new Builder() // + .withId(pCloudCloud.id()) // + .withAccessToken(pCloudCloud.accessToken()) // + .withUrl(pCloudCloud.url()) // + .withUsername(pCloudCloud.username()); + } + + @Override + public Long id() { + return id; + } + + public String accessToken() { + return accessToken; + } + + public String url() { + return url; + } + + public String username() { + return username; + } + + @Override + public CloudType type() { + return CloudType.DROPBOX; + } + + @Override + public boolean configurationMatches(Cloud cloud) { + return true; + } + + @Override + public boolean predefined() { + return true; + } + + @Override + public boolean persistent() { + return true; + } + + @Override + public boolean requiresNetwork() { + return true; + } + + @NotNull + @Override + public String toString() { + return "PCLOUD"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } + if (obj == this) { + return true; + } + return internalEquals((PCloudCloud) obj); + } + + @Override + public int hashCode() { + return id == null ? 0 : id.hashCode(); + } + + private boolean internalEquals(PCloudCloud obj) { + return id != null && id.equals(obj.id); + } + + public static class Builder { + + private Long id; + private String accessToken; + private String url; + private String username; + + private Builder() { + } + + public Builder withId(Long id) { + this.id = id; + return this; + } + + public Builder withAccessToken(String accessToken) { + this.accessToken = accessToken; + return this; + } + + public Builder withUrl(String url) { + this.url = url; + return this; + } + + public Builder withUsername(String username) { + this.username = username; + return this; + } + + public PCloudCloud build() { + return new PCloudCloud(this); + } + + } + +} From 32ce52c93dd897cb2b7d54200d415f714b3487e5 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 12 Mar 2021 16:04:14 +0100 Subject: [PATCH 23/93] feat(Domain): introduce PCLOUD CloudType --- domain/src/main/java/org/cryptomator/domain/CloudType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/src/main/java/org/cryptomator/domain/CloudType.java b/domain/src/main/java/org/cryptomator/domain/CloudType.java index 5161e418..b2df0cf8 100644 --- a/domain/src/main/java/org/cryptomator/domain/CloudType.java +++ b/domain/src/main/java/org/cryptomator/domain/CloudType.java @@ -2,6 +2,6 @@ package org.cryptomator.domain; public enum CloudType { - DROPBOX, GOOGLE_DRIVE, ONEDRIVE, WEBDAV, LOCAL, CRYPTO + DROPBOX, GOOGLE_DRIVE, ONEDRIVE, PCLOUD, WEBDAV, LOCAL, CRYPTO } From 9329062b54bd19fa75067a5a851c54dbfc9d36d8 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 12 Mar 2021 16:05:13 +0100 Subject: [PATCH 24/93] feat(Data): introduce PCloudCloudContentRepositoryFactory --- .../PCloudCloudContentRepositoryFactory.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepositoryFactory.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepositoryFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepositoryFactory.java new file mode 100644 index 00000000..a666af0c --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepositoryFactory.java @@ -0,0 +1,35 @@ +package org.cryptomator.data.cloud.pcloud; + +import android.content.Context; + +import org.cryptomator.data.repository.CloudContentRepositoryFactory; +import org.cryptomator.domain.Cloud; +import org.cryptomator.domain.PCloudCloud; +import org.cryptomator.domain.repository.CloudContentRepository; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import static org.cryptomator.domain.CloudType.PCLOUD; + +@Singleton +public class PCloudCloudContentRepositoryFactory implements CloudContentRepositoryFactory { + + private final Context context; + + @Inject + public PCloudCloudContentRepositoryFactory(Context context) { + this.context = context; + } + + @Override + public boolean supports(Cloud cloud) { + return cloud.type() == PCLOUD; + } + + @Override + public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) { + return new PCloudCloudContentRepository((PCloudCloud) cloud, context); + } + +} From bb4572b9d01e0ee521cca4d842b9e8e642c76eaf Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 12 Mar 2021 16:06:43 +0100 Subject: [PATCH 25/93] feat(Data): introduce PCloudCloudNodeFactory --- .../cloud/pcloud/PCloudCloudNodeFactory.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudNodeFactory.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudNodeFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudNodeFactory.java new file mode 100644 index 00000000..2294d63a --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudNodeFactory.java @@ -0,0 +1,40 @@ +package org.cryptomator.data.cloud.pcloud; + +import com.pcloud.sdk.RemoteEntry; +import com.pcloud.sdk.RemoteFile; +import com.pcloud.sdk.RemoteFolder; + +import org.cryptomator.util.Optional; + +class PCloudCloudNodeFactory { + + public static PCloudFile from(PCloudFolder parent, RemoteFile metadata) { + + return new PCloudFile(parent, metadata.fileId(), metadata.name(), getNodePath(parent, metadata.name()), Optional.ofNullable(metadata.size()), Optional.ofNullable(metadata.lastModified())); + } + + public static PCloudFile file(PCloudFolder parent, String name, Optional size, String path) { + return new PCloudFile(parent, null, name, path, size, Optional.empty()); + } + + public static PCloudFolder from(PCloudFolder parent, RemoteFolder metadata) { + return new PCloudFolder(parent, metadata.folderId(), metadata.name(), getNodePath(parent, metadata.name())); + } + + private static String getNodePath(PCloudFolder parent, String name) { + return parent.getPath() + "/" + name; + } + + public static PCloudFolder folder(PCloudFolder parent, String name, String path) { + return new PCloudFolder(parent, null, name, path); + } + + public static PCloudNode from(PCloudFolder parent, RemoteEntry metadata) { + if (metadata instanceof RemoteFile) { + return from(parent, (RemoteFile) metadata); + } else { + return from(parent, (RemoteFolder) metadata); + } + } + +} From 80dce5ec60492c03c40d5b646afc5e029a9fb94b Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 12 Mar 2021 17:15:03 +0100 Subject: [PATCH 26/93] feat: add pCloud implementation --- .../cloud/pcloud/PCloudApiErrorCodes.java | 37 +++ .../cloud/pcloud/PCloudClientFactory.java | 54 ++++ .../pcloud/PCloudCloudContentRepository.java | 209 +++++++++++++++ .../data/cloud/pcloud/PCloudImpl.java | 243 ++++++++++++++++++ .../cryptomator/util/file/LruFileCacheUtil.kt | 3 +- 5 files changed, 545 insertions(+), 1 deletion(-) create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java new file mode 100644 index 00000000..f444349f --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java @@ -0,0 +1,37 @@ +package org.cryptomator.data.cloud.pcloud; + +public enum PCloudApiErrorCodes { + LOGIN_REQUIRED(1000), + NO_FULL_PATH_OR_NAME_FOLDER_ID_PROVIDED(1001), + NO_FULL_PATH_OR_FOLDER_ID_PROVIDED(1002), + NO_DESTINATION_PROVIDED(1016), + INVALID_FOLDER_ID(1017), + INVALID_DESTINATION(1037), + PROVIDE_URL(1040), + LOGIN_FAILED(2000), + INVALID_FILE_FOLDER_NAME(2001), + COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST(2002), + ACCESS_DENIED(2003), + FILE_OR_FOLDER_ALREADY_EXISTS(2004), + DIRECTORY_DOES_NOT_EXIST(2005), + FOLDER_NOT_EMPTY(2006), + CANNOT_DELETE_ROOT_FOLDER(2007), + USER_OVER_QUOTA(2008), + FILE_NOT_FOUND(2009), + SHARED_FOLDER_IN_SHARED_FOLDER(2023), + ACTIVE_SHARES_OR_SHAREREQUESTS_PRESENT(2028), + CANNOT_RENAME_ROOT_FOLDER(2042), + CANNOT_MOVE_FOLDER_INTO_SUBFOLDER_OF_ITSELF(2043), + INVALID_ACCESS_TOKEN(2094), + TOO_MANY_LOGIN_TRIES_FROM_IP(4000), + INTERNAL_ERROR(5000), + INTERNAL_UPLOAD_ERROR(5001); + + private final int value; + + PCloudApiErrorCodes(final int newValue) { + value = newValue; + } + + public int getValue() { return value; } +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java new file mode 100644 index 00000000..8c943367 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java @@ -0,0 +1,54 @@ +package org.cryptomator.data.cloud.pcloud; + +import android.content.Context; + +import com.pcloud.sdk.ApiClient; +import com.pcloud.sdk.Authenticators; +import com.pcloud.sdk.PCloudSdk; + +import org.cryptomator.data.cloud.okhttplogging.HttpLoggingInterceptor; +import org.cryptomator.util.SharedPreferencesHandler; +import org.cryptomator.util.file.LruFileCacheUtil; + +import okhttp3.Cache; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import timber.log.Timber; + +import static org.cryptomator.data.util.NetworkTimeout.CONNECTION; +import static org.cryptomator.data.util.NetworkTimeout.READ; +import static org.cryptomator.data.util.NetworkTimeout.WRITE; + +class PCloudClientFactory { + + private ApiClient apiClient; + + private static Interceptor httpLoggingInterceptor(Context context) { + return new HttpLoggingInterceptor(message -> Timber.tag("OkHttp").d(message), context); + } + + public ApiClient getClient(String accessToken, String url, Context context) { + if (apiClient == null) { + final SharedPreferencesHandler sharedPreferencesHandler = new SharedPreferencesHandler(context); + apiClient = createApiClient(accessToken, url, context, sharedPreferencesHandler.useLruCache(), sharedPreferencesHandler.lruCacheSize()); + } + return apiClient; + } + + private ApiClient createApiClient(String accessToken, String url, Context context, boolean useLruCache, int lruCacheSize) { + OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient() // + .newBuilder() // + .connectTimeout(CONNECTION.getTimeout(), CONNECTION.getUnit()) // + .readTimeout(READ.getTimeout(), READ.getUnit()) // + .writeTimeout(WRITE.getTimeout(), WRITE.getUnit()) // + .addInterceptor(httpLoggingInterceptor(context)); //; + + if (useLruCache) { + okHttpClientBuilder.cache(new Cache(new LruFileCacheUtil(context).resolve(LruFileCacheUtil.Cache.PCLOUD), lruCacheSize)); + } + + OkHttpClient okHttpClient = okHttpClientBuilder.build(); + + return PCloudSdk.newClientBuilder().authenticator(Authenticators.newOAuthAuthenticator(accessToken)).withClient(okHttpClient).apiHost(url).create(); + } +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java new file mode 100644 index 00000000..0f5f6190 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java @@ -0,0 +1,209 @@ +package org.cryptomator.data.cloud.pcloud; + +import android.content.Context; + +import com.pcloud.sdk.ApiError; + +import org.cryptomator.data.cloud.InterceptingCloudContentRepository; +import org.cryptomator.domain.PCloudCloud; +import org.cryptomator.domain.exception.BackendException; +import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; +import org.cryptomator.domain.exception.FatalBackendException; +import org.cryptomator.domain.exception.NetworkConnectionException; +import org.cryptomator.domain.exception.NoSuchCloudFileException; +import org.cryptomator.domain.exception.authentication.WrongCredentialsException; +import org.cryptomator.domain.repository.CloudContentRepository; +import org.cryptomator.domain.usecases.ProgressAware; +import org.cryptomator.domain.usecases.cloud.DataSource; +import org.cryptomator.domain.usecases.cloud.DownloadState; +import org.cryptomator.domain.usecases.cloud.UploadState; +import org.cryptomator.util.Optional; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +import static org.cryptomator.util.ExceptionUtil.contains; + +class PCloudCloudContentRepository extends InterceptingCloudContentRepository { + + private final PCloudCloud cloud; + + public PCloudCloudContentRepository(PCloudCloud cloud, Context context) { + super(new Intercepted(cloud, context)); + this.cloud = cloud; + } + + @Override + protected void throwWrappedIfRequired(Exception e) throws BackendException { + throwConnectionErrorIfRequired(e); + throwWrongCredentialsExceptionIfRequired(e); + } + + private void throwConnectionErrorIfRequired(Exception e) throws NetworkConnectionException { + if (contains(e, IOException.class)) { + throw new NetworkConnectionException(e); + } + } + + private void throwWrongCredentialsExceptionIfRequired(Exception e) { + if (e instanceof ApiError && ((ApiError) e).errorCode() == PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue()) { + throw new WrongCredentialsException(cloud); + } + } + + private static class Intercepted implements CloudContentRepository { + + private final PCloudImpl cloud; + + public Intercepted(PCloudCloud cloud, Context context) { + this.cloud = new PCloudImpl(cloud, context); + } + + public PCloudFolder root(PCloudCloud cloud) { + return this.cloud.root(); + } + + @Override + public PCloudFolder resolve(PCloudCloud cloud, String path) { + return this.cloud.resolve(path); + } + + @Override + public PCloudFile file(PCloudFolder parent, String name) { + return cloud.file(parent, name); + } + + @Override + public PCloudFile file(PCloudFolder parent, String name, Optional size) throws BackendException { + return cloud.file(parent, name, size); + } + + @Override + public PCloudFolder folder(PCloudFolder parent, String name) { + return cloud.folder(parent, name); + } + + @Override + public boolean exists(PCloudNode node) throws BackendException { + try { + return cloud.exists(node); + } catch (ApiError|IOException e) { + throw new FatalBackendException(e); + } + } + + @Override + public List list(PCloudFolder folder) throws BackendException { + try { + return cloud.list(folder); + } catch (ApiError | IOException e) { + if (e instanceof ApiError) { + if (((ApiError) e).errorCode() == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue()) { + throw new NoSuchCloudFileException(); + } + } + throw new FatalBackendException(e); + } + } + + @Override + public PCloudFolder create(PCloudFolder folder) throws BackendException { + try { + return cloud.create(folder); + } catch (ApiError | IOException e) { + if (e instanceof ApiError) { + if (((ApiError) e).errorCode() == PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue()) + throw new CloudNodeAlreadyExistsException(folder.getName()); + } + throw new FatalBackendException(e); + } + } + + @Override + public PCloudFolder move(PCloudFolder source, PCloudFolder target) throws BackendException { + try { + return (PCloudFolder) cloud.move(source, target); + } catch (ApiError | IOException e) { + if (e instanceof ApiError) { + if (((ApiError)e).errorCode() == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue()) { + throw new NoSuchCloudFileException(source.getName()); + } else if (((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue()) { + throw new CloudNodeAlreadyExistsException(target.getName()); + } + throw new CloudNodeAlreadyExistsException(target.getName()); + } + throw new FatalBackendException(e); + } + } + + @Override + public PCloudFile move(PCloudFile source, PCloudFile target) throws BackendException { + try { + return (PCloudFile) cloud.move(source, target); + } catch (ApiError | IOException e) { + if (e instanceof ApiError) { + if (((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) { + throw new NoSuchCloudFileException(source.getName()); + } else if (((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue()) { + throw new CloudNodeAlreadyExistsException(target.getName()); + } + throw new CloudNodeAlreadyExistsException(target.getName()); + } + throw new FatalBackendException(e); + } + } + + @Override + public PCloudFile write(PCloudFile uploadFile, DataSource data, ProgressAware progressAware, boolean replace, long size) throws BackendException { + try { + return cloud.write(uploadFile, data, progressAware, replace, size); + } catch (ApiError | IOException e) { + if (((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) { + throw new NoSuchCloudFileException(uploadFile.getName()); + } + throw new FatalBackendException(e); + } + } + + @Override + public void read(PCloudFile file, Optional encryptedTmpFile, OutputStream data, ProgressAware progressAware) throws BackendException { + try { + cloud.read(file, data, progressAware); + } catch (ApiError | IOException e) { + if (((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) { + throw new NoSuchCloudFileException(file.getName()); + } + throw new FatalBackendException(e); + } + } + + @Override + public void delete(PCloudNode node) throws BackendException { + try { + cloud.delete(node); + } catch (ApiError | IOException e) { + if (((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) { + throw new NoSuchCloudFileException(node.getName()); + } + throw new FatalBackendException(e); + } + } + + @Override + public String checkAuthenticationAndRetrieveCurrentAccount(PCloudCloud cloud) throws BackendException { + try { + return this.cloud.currentAccount(); + } catch (ApiError | IOException e) { + throw new FatalBackendException(e); + } + } + + @Override + public void logout(PCloudCloud cloud) throws BackendException { + // empty + } + } + +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java new file mode 100644 index 00000000..83251861 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -0,0 +1,243 @@ +package org.cryptomator.data.cloud.pcloud; + +import android.content.Context; + +import com.pcloud.sdk.ApiClient; +import com.pcloud.sdk.ApiError; +import com.pcloud.sdk.DataSink; +import com.pcloud.sdk.DownloadOptions; +import com.pcloud.sdk.FileLink; +import com.pcloud.sdk.ProgressListener; +import com.pcloud.sdk.RemoteEntry; +import com.pcloud.sdk.RemoteFile; +import com.pcloud.sdk.RemoteFolder; +import com.pcloud.sdk.UploadOptions; +import com.pcloud.sdk.UserInfo; + +import org.cryptomator.data.util.CopyStream; +import org.cryptomator.domain.CloudFile; +import org.cryptomator.domain.CloudFolder; +import org.cryptomator.domain.CloudNode; +import org.cryptomator.domain.PCloudCloud; +import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; +import org.cryptomator.domain.exception.authentication.NoAuthenticationProvidedException; +import org.cryptomator.domain.usecases.ProgressAware; +import org.cryptomator.domain.usecases.cloud.DataSource; +import org.cryptomator.domain.usecases.cloud.DownloadState; +import org.cryptomator.domain.usecases.cloud.Progress; +import org.cryptomator.domain.usecases.cloud.UploadState; +import org.cryptomator.util.Optional; +import org.cryptomator.util.crypto.CredentialCryptor; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import okio.BufferedSink; +import okio.BufferedSource; +import okio.Okio; +import okio.Source; + +import static org.cryptomator.domain.usecases.cloud.Progress.progress; + +class PCloudImpl { + + private static final int DIRECTORY_DOES_NOT_EXIST = 2005; + private static final int INVALID_FILE_OR_FOLDER_NAME = 2001; + + private final PCloudClientFactory clientFactory = new PCloudClientFactory(); + private final PCloudCloud cloud; + private final RootPCloudFolder root; + private final Context context; + + PCloudImpl(PCloudCloud cloud, Context context) { + if (cloud.accessToken() == null) { + throw new NoAuthenticationProvidedException(cloud); + } + this.cloud = cloud; + this.root = new RootPCloudFolder(cloud); + this.context = context; + } + + private ApiClient client() { + return clientFactory.getClient(decrypt(cloud.accessToken()), cloud.url(), context); + } + + private String decrypt(String password) { + return CredentialCryptor // + .getInstance(context) // + .decrypt(password); + } + + public PCloudFolder root() { + return root; + } + + public PCloudFolder resolve(String path) { + if (path.startsWith("/")) { + path = path.substring(1); + } + String[] names = path.split("/"); + PCloudFolder folder = root; + for (String name : names) { + folder = folder(folder, name); + } + return folder; + } + + public PCloudFile file(CloudFolder folder, String name) { + return file(folder, name, Optional.empty()); + } + + public PCloudFile file(CloudFolder folder, String name, Optional size) { + return PCloudCloudNodeFactory.file( // + (PCloudFolder) folder, // + name, // + size, // + folder.getPath() + '/' + name); + } + + public PCloudFolder folder(CloudFolder folder, String name) { + return PCloudCloudNodeFactory.folder( // + (PCloudFolder) folder, // + name, // + folder.getPath() + '/' + name); + } + + public boolean exists(CloudNode node) throws ApiError, IOException { + try { + if (node instanceof PCloudFolder) { + client().listFolder(((PCloudFolder) node).getPath()).execute(); + return true; + } else { + client().stat(((PCloudFile)node).getPath()).execute(); + return true; + } + } catch (ApiError e) { + if (e.errorCode() == DIRECTORY_DOES_NOT_EXIST || e.errorCode() == INVALID_FILE_OR_FOLDER_NAME) { + return false; + } + throw e; + } + } + + public List list(CloudFolder folder) throws ApiError, IOException { + List result = new ArrayList<>(); + RemoteFolder listFolderResult = null; + List entryMetadata = listFolderResult.children(); + for (RemoteEntry metadata : entryMetadata) { + result.add(PCloudCloudNodeFactory.from( // + (PCloudFolder) folder, // + metadata)); + } + return result; + } + + public PCloudFolder create(CloudFolder folder) throws ApiError, IOException { + RemoteFolder createFolderResult = client() // + .createFolder(((PCloudFolder)folder.getParent()).getId(), folder.getName()) // + .execute(); + + return PCloudCloudNodeFactory.from( // + (PCloudFolder) folder.getParent(), // + createFolderResult.asFolder()); + } + + public CloudNode move(CloudNode source, CloudNode target) throws ApiError, IOException { + RemoteEntry relocationResult; + if (source instanceof PCloudFolder) { + relocationResult = client().moveFolder(((PCloudFolder) source).getId(), ((PCloudFolder) target).getId()).execute(); + } else { + relocationResult = client().moveFile(((PCloudFile) source).getId(), ((PCloudFolder) target).getId()).execute(); + } + + return PCloudCloudNodeFactory.from( // + (PCloudFolder) target.getParent(), // + relocationResult); + } + + public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) throws ApiError, IOException, CloudNodeAlreadyExistsException { + if (exists(file) && !replace) { + throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false"); + } + + progressAware.onProgress(Progress.started(UploadState.upload(file))); + UploadOptions uploadOptions = UploadOptions.DEFAULT; + if (replace) { + uploadOptions = UploadOptions.OVERRIDE_FILE; + } + + RemoteFile uploadedFile = uploadFile(file, data, progressAware, uploadOptions, size); + + progressAware.onProgress(Progress.completed(UploadState.upload(file))); + + return PCloudCloudNodeFactory.from( // + file.getParent(), // + uploadedFile); + } + + private RemoteFile uploadFile(final PCloudFile file, DataSource data, final ProgressAware progressAware, UploadOptions uploadOptions, final long size) // + throws ApiError, IOException { + ProgressListener listener = (done, total) -> progressAware.onProgress( // + progress(UploadState.upload(file)) // + .between(0) // + .and(size) // + .withValue(done)); + + com.pcloud.sdk.DataSource pCloudDataSource = new com.pcloud.sdk.DataSource() { + @Override + public void writeTo(BufferedSink sink) throws IOException { + try (Source source = Okio.source(data.open(context))) { + sink.writeAll(source); + } + } + }; + + return client() // + .createFile(((PCloudFolder) file.getParent()).getId(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // + .execute(); + } + + public void read(CloudFile file, OutputStream data, final ProgressAware progressAware) throws ApiError, IOException { + progressAware.onProgress(Progress.started(DownloadState.download(file))); + + FileLink fileLink = client().createFileLink(((PCloudFile) file).getId(), DownloadOptions.DEFAULT).execute(); + + ProgressListener listener = (done, total) -> progressAware.onProgress( // + progress(DownloadState.download(file)) // + .between(0) // + .and(file.getSize().orElse(Long.MAX_VALUE)) // + .withValue(done)); + + DataSink sink = new DataSink() { + @Override + public void readAll(BufferedSource source) throws IOException { + CopyStream.copyStreamToStream(source.getBuffer().inputStream(), data); + } + }; + + client().download(fileLink, sink, listener).execute(); + + progressAware.onProgress(Progress.completed(DownloadState.download(file))); + } + + public void delete(CloudNode node) throws ApiError, IOException { + if (node instanceof PCloudFolder) { + client() // + .deleteFolder(((PCloudFolder) node).getId()).execute(); + } else { + client() // + .deleteFile(((PCloudFile) node).getId()).execute(); + } + + } + + public String currentAccount() throws ApiError, IOException { + UserInfo currentAccount = client() // + .getUserInfo() // + .execute(); + return currentAccount.email(); + } +} diff --git a/util/src/main/java/org/cryptomator/util/file/LruFileCacheUtil.kt b/util/src/main/java/org/cryptomator/util/file/LruFileCacheUtil.kt index f2ea8bf7..c4082926 100644 --- a/util/src/main/java/org/cryptomator/util/file/LruFileCacheUtil.kt +++ b/util/src/main/java/org/cryptomator/util/file/LruFileCacheUtil.kt @@ -21,13 +21,14 @@ class LruFileCacheUtil(context: Context) { private val parent: File = context.cacheDir enum class Cache { - DROPBOX, WEBDAV, ONEDRIVE, GOOGLE_DRIVE + DROPBOX, WEBDAV, PCLOUD, ONEDRIVE, GOOGLE_DRIVE } fun resolve(cache: Cache?): File { return when (cache) { Cache.DROPBOX -> File(parent, "LruCacheDropbox") Cache.WEBDAV -> File(parent, "LruCacheWebdav") + Cache.PCLOUD -> File(parent, "LruCachePCloud") Cache.ONEDRIVE -> File(parent, "LruCacheOneDrive") Cache.GOOGLE_DRIVE -> File(parent, "LruCacheGoogleDrive") else -> throw IllegalStateException() From faaec7a9224bde8916e84931a3efdae444c3fe0d Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 12 Mar 2021 17:19:02 +0100 Subject: [PATCH 27/93] feat: add pCloud model and mappers, add strings --- .../data/db/mappers/CloudEntityMapper.java | 13 ++++++++++ .../CloudContentRepositoryFactories.java | 3 +++ .../domain/usecases/cloud/LogoutCloud.java | 7 ++++++ .../presentation/model/CloudTypeModel.kt | 3 +++ .../presentation/model/PCloudCloudModel.kt | 24 +++++++++++++++++++ .../model/mappers/CloudModelMapper.kt | 2 ++ presentation/src/main/res/values/strings.xml | 1 + 7 files changed, 53 insertions(+) create mode 100644 presentation/src/main/java/org/cryptomator/presentation/model/PCloudCloudModel.kt diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java index f369ba5c..f9daa0b2 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java @@ -4,6 +4,7 @@ import org.cryptomator.data.db.entities.CloudEntity; import org.cryptomator.domain.Cloud; import org.cryptomator.domain.CloudType; import org.cryptomator.domain.DropboxCloud; +import org.cryptomator.domain.PCloudCloud; import org.cryptomator.domain.GoogleDriveCloud; import org.cryptomator.domain.LocalStorageCloud; import org.cryptomator.domain.OnedriveCloud; @@ -16,6 +17,7 @@ import static org.cryptomator.domain.DropboxCloud.aDropboxCloud; import static org.cryptomator.domain.GoogleDriveCloud.aGoogleDriveCloud; import static org.cryptomator.domain.LocalStorageCloud.aLocalStorage; import static org.cryptomator.domain.OnedriveCloud.aOnedriveCloud; +import static org.cryptomator.domain.PCloudCloud.aPCloudCloud; import static org.cryptomator.domain.WebDavCloud.aWebDavCloudCloud; @Singleton @@ -47,6 +49,12 @@ public class CloudEntityMapper extends EntityMapper { .withAccessToken(entity.getAccessToken()) // .withUsername(entity.getUsername()) // .build(); + case PCLOUD: + return aPCloudCloud() // + .withId(entity.getId()) // + .withAccessToken(entity.getAccessToken()) // + .withUsername(entity.getUsername()) // + .build(); case LOCAL: return aLocalStorage() // .withId(entity.getId()) // @@ -82,6 +90,11 @@ public class CloudEntityMapper extends EntityMapper { result.setAccessToken(((OnedriveCloud) domainObject).accessToken()); result.setUsername(((OnedriveCloud) domainObject).username()); break; + case PCLOUD: + result.setAccessToken(((PCloudCloud) domainObject).accessToken()); + result.setWebdavUrl(((PCloudCloud) domainObject).url()); + result.setUsername(((PCloudCloud) domainObject).username()); + break; case LOCAL: result.setAccessToken(((LocalStorageCloud) domainObject).rootUri()); break; diff --git a/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java b/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java index 955822ab..eea89b15 100644 --- a/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java +++ b/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java @@ -5,6 +5,7 @@ import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory; import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory; import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory; import org.cryptomator.data.cloud.onedrive.OnedriveCloudContentRepositoryFactory; +import org.cryptomator.data.cloud.pcloud.PCloudCloudContentRepositoryFactory; import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory; import org.cryptomator.data.repository.CloudContentRepositoryFactory; import org.jetbrains.annotations.NotNull; @@ -25,6 +26,7 @@ public class CloudContentRepositoryFactories implements Iterable() CloudTypeModel.DROPBOX -> DropboxCloudModel(domainObject) CloudTypeModel.GOOGLE_DRIVE -> GoogleDriveCloudModel(domainObject) CloudTypeModel.ONEDRIVE -> OnedriveCloudModel(domainObject) + CloudTypeModel.PCLOUD -> PCloudCloudModel(domainObject) CloudTypeModel.CRYPTO -> CryptoCloudModel(domainObject) CloudTypeModel.LOCAL -> LocalStorageModel(domainObject) CloudTypeModel.WEBDAV -> WebDavCloudModel(domainObject) diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index c7f0b4d3..b7df4edb 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ Dropbox Google Drive OneDrive + pCloud WebDAV Local storage From e2903820f71581225f285bec6ce9a5ff98372ebc Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 14:38:54 +0100 Subject: [PATCH 28/93] fix: add missing withUrl() for pCloud --- .../java/org/cryptomator/data/db/mappers/CloudEntityMapper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java index f9daa0b2..da4f3fbf 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java @@ -52,6 +52,7 @@ public class CloudEntityMapper extends EntityMapper { case PCLOUD: return aPCloudCloud() // .withId(entity.getId()) // + .withUrl(entity.getWebdavUrl()) // .withAccessToken(entity.getAccessToken()) // .withUsername(entity.getUsername()) // .build(); From 9e3d0a91dc393a80e49c01c1a7026997a3745ba4 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 14:44:08 +0100 Subject: [PATCH 29/93] fix: return proper type --- domain/src/main/java/org/cryptomator/domain/PCloudCloud.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java b/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java index 2bc990e4..2518870a 100644 --- a/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java @@ -47,7 +47,7 @@ public class PCloudCloud implements Cloud { @Override public CloudType type() { - return CloudType.DROPBOX; + return CloudType.PCLOUD; } @Override From 85e2b16232f0caab804b76a794e736b984518e25 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 14:47:05 +0100 Subject: [PATCH 30/93] fix: add id() and url() --- .../cryptomator/presentation/model/PCloudCloudModel.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/PCloudCloudModel.kt b/presentation/src/main/java/org/cryptomator/presentation/model/PCloudCloudModel.kt index 9407ab9b..a0042831 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/model/PCloudCloudModel.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/model/PCloudCloudModel.kt @@ -14,6 +14,14 @@ class PCloudCloudModel(cloud: Cloud) : CloudModel(cloud) { return cloud().username() } + fun url(): String { + return cloud().url() + } + + fun id(): Long { + return cloud().id() + } + private fun cloud(): PCloudCloud { return toCloud() as PCloudCloud } From c9a73ced8f2c268ec57b17ee31345f4f39350e59 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 14:49:44 +0100 Subject: [PATCH 31/93] feat: support multi instances, fix display name resource - Allow multiple instances for pCloud - Use correct display name resource --- .../org/cryptomator/presentation/model/CloudTypeModel.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt index be2100d2..081c739c 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt @@ -18,9 +18,11 @@ enum class CloudTypeModel(builder: Builder) { .withCloudImageResource(R.drawable.onedrive) // .withVaultImageResource(R.drawable.onedrive_vault) // .withVaultSelectedImageResource(R.drawable.onedrive_vault_selected)), // - PCLOUD(Builder("PCLOUD", R.string.cloud_names_dropbox) // - .withCloudImageResource(R.drawable.cloud_type_dropbox) // - .withCloudImageLargeResource(R.drawable.cloud_type_dropbox_large)), // + PCLOUD(Builder("PCLOUD", R.string.cloud_names_pcloud) // + //TODO: change icons for pCloud + .withCloudImageResource(R.drawable.storage_type_local) // + .withCloudImageLargeResource(R.drawable.storage_type_local_large) // + .withMultiInstances()), // WEBDAV(Builder("WEBDAV", R.string.cloud_names_webdav) // .withCloudImageResource(R.drawable.webdav) // .withVaultImageResource(R.drawable.webdav_vault) // From 6a249056b0caf2114712f193f26d916c52a79827 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 14:53:52 +0100 Subject: [PATCH 32/93] fix: set url to null, add TODO --- .../java/org/cryptomator/domain/usecases/cloud/LogoutCloud.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/LogoutCloud.java b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/LogoutCloud.java index 5bfb0b0e..661acd6a 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/LogoutCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/LogoutCloud.java @@ -49,10 +49,12 @@ class LogoutCloud { .withAccessToken(null) // .build(); } else if (cloud instanceof PCloudCloud) { + //TODO proper logout? return PCloudCloud // .aCopyOf((PCloudCloud) cloud) // .withUsername(null) // .withAccessToken(null) // + .withUrl(null) // .build(); } throw new IllegalStateException("Logout not supported for cloud with type " + cloud.type()); From 550415627eeb1e7ec9cb4a5976da9ad575d3674a Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 14:55:05 +0100 Subject: [PATCH 33/93] feat: add authentication for pCloud (including UI) --- .../usecases/cloud/ConnectToPCloud.java | 23 +++++ presentation/build.gradle | 12 ++- .../presenter/CloudConnectionListPresenter.kt | 92 +++++++++++++++++++ .../presenter/CloudSettingsPresenter.kt | 15 ++- .../ui/adapter/CloudConnectionListAdapter.kt | 8 ++ .../ui/adapter/CloudSettingsAdapter.kt | 6 ++ .../src/main/res/values-de/strings.xml | 1 + .../src/main/res/values-es/strings.xml | 1 + .../src/main/res/values-fr/strings.xml | 1 + .../src/main/res/values-tr/strings.xml | 1 + presentation/src/main/res/values/strings.xml | 1 + .../presenter/AuthenticateCloudPresenter.kt | 22 +++++ 12 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToPCloud.java diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToPCloud.java b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToPCloud.java new file mode 100644 index 00000000..a1cca2b3 --- /dev/null +++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ConnectToPCloud.java @@ -0,0 +1,23 @@ +package org.cryptomator.domain.usecases.cloud; + +import org.cryptomator.domain.PCloudCloud; +import org.cryptomator.domain.exception.BackendException; +import org.cryptomator.domain.repository.CloudContentRepository; +import org.cryptomator.generator.Parameter; +import org.cryptomator.generator.UseCase; + +@UseCase +class ConnectToPCloud { + + private final CloudContentRepository cloudContentRepository; + private final PCloudCloud cloud; + + public ConnectToPCloud(CloudContentRepository cloudContentRepository, @Parameter PCloudCloud cloud) { + this.cloudContentRepository = cloudContentRepository; + this.cloud = cloud; + } + + public void execute() throws BackendException { + cloudContentRepository.checkAuthenticationAndRetrieveCurrentAccount(cloud); + } +} diff --git a/presentation/build.gradle b/presentation/build.gradle index 805ca2f8..b43b1b19 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -50,7 +50,11 @@ android { useProguard false buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY') + "\"" - manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY')] + buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID') + "\"" + manifestPlaceholders = [ + DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY'), + PCLOUD_CLIENT_ID: getApiKey('PCLOUD_CLIENT_ID') + ] resValue "string", "app_id", androidApplicationId } @@ -64,7 +68,11 @@ android { testCoverageEnabled false buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY_DEBUG') + "\"" - manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY_DEBUG')] + buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID_DEBUG') + "\"" + manifestPlaceholders = [ + DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY_DEBUG'), + PCLOUD_CLIENT_ID: getApiKey('PCLOUD_CLIENT_ID_DEBUG') + ] applicationIdSuffix ".debug" versionNameSuffix '-DEBUG' diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index fae5629b..da62bc5c 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -4,14 +4,21 @@ import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri import android.os.Build +import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi +import com.pcloud.sdk.AuthorizationActivity +import com.pcloud.sdk.AuthorizationData +import com.pcloud.sdk.AuthorizationRequest +import com.pcloud.sdk.AuthorizationResult import org.cryptomator.domain.Cloud import org.cryptomator.domain.LocalStorageCloud +import org.cryptomator.domain.PCloudCloud import org.cryptomator.domain.Vault import org.cryptomator.domain.di.PerView import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase +import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase import org.cryptomator.domain.usecases.cloud.RemoveCloudUseCase import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase import org.cryptomator.domain.usecases.vault.GetVaultListUseCase @@ -26,6 +33,7 @@ import org.cryptomator.presentation.model.WebDavCloudModel import org.cryptomator.presentation.model.mappers.CloudModelMapper import org.cryptomator.presentation.ui.activity.view.CloudConnectionListView import org.cryptomator.presentation.workflow.ActivityResult +import org.cryptomator.util.crypto.CredentialCryptor import java.util.* import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject @@ -34,6 +42,7 @@ import timber.log.Timber @PerView class CloudConnectionListPresenter @Inject constructor( // private val getCloudsUseCase: GetCloudsUseCase, // + private val getUsernameUseCase: GetUsernameUseCase, // private val removeCloudUseCase: RemoveCloudUseCase, // private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, // private val getVaultListUseCase: GetVaultListUseCase, // @@ -122,6 +131,18 @@ class CloudConnectionListPresenter @Inject constructor( // when (selectedCloudType.get()) { CloudTypeModel.WEBDAV -> requestActivityResult(ActivityResultCallbacks.addChangeWebDavCloud(), // Intents.webDavAddOrChangeIntent()) + CloudTypeModel.PCLOUD -> { + val authIntent: Intent = AuthorizationActivity.createIntent( + this.context(), + AuthorizationRequest.create() + .setType(AuthorizationRequest.Type.TOKEN) + .setClientId("tsAamgqqwk7") + .setForceAccessApproval(true) + .addPermission("manageshares") + .build()) + requestActivityResult(ActivityResultCallbacks.pCloudAuthenticationFinished(), // + authIntent) + } CloudTypeModel.LOCAL -> openDocumentTree() } } @@ -162,6 +183,77 @@ class CloudConnectionListPresenter @Inject constructor( // loadCloudList() } + @Callback + fun pCloudAuthenticationFinished(activityResult: ActivityResult?) { + val authData: AuthorizationData = AuthorizationActivity.getResult(activityResult!!.intent()) + val result: AuthorizationResult = authData.result + + when (result) { + AuthorizationResult.ACCESS_GRANTED -> { + val accessToken: String = CredentialCryptor // + .getInstance(this.context()) // + .encrypt(authData.token) + val pCloudSkeleton: PCloudCloud = PCloudCloud.aPCloudCloud() // + .withAccessToken(accessToken) + .withUrl(authData.apiHost) + .build(); + getUsernameUseCase // + .withCloud(pCloudSkeleton) // + .run(object : DefaultResultHandler() { + override fun onSuccess(username: String?) { + prepareForSavingPCloudCloud(PCloudCloud.aCopyOf(pCloudSkeleton).withUsername(username).build()) + } + }) + + Log.d("pCloud", "Account access granted, authData:\n$authData") + + } + AuthorizationResult.ACCESS_DENIED -> //TODO: Add proper handling for denied grants. + Log.d("pCloud", "Account access denied") + AuthorizationResult.AUTH_ERROR -> { + //TODO: Add error handling. + Log.d("pCloud", """Account access grant error: ${authData.errorMessage}""".trimIndent()) + } + AuthorizationResult.CANCELLED -> { + //TODO: Handle cancellation. + Log.d("pCloud", "Account access grant cancelled:") + } + } + } + + fun prepareForSavingPCloudCloud(cloud: PCloudCloud) { + getCloudsUseCase // + .withCloudType(CloudTypeModel.valueOf(selectedCloudType.get())) // + .run(object : DefaultResultHandler>() { + override fun onSuccess(clouds: List) { + // here check if a cloud with the same mail adress already exists, + // if so update (in case of the token changed) else create a new one + val existingPCloudCloud: PCloudCloud? = clouds.firstOrNull { + (it as PCloudCloud).username() == cloud.username() + } as PCloudCloud? + + if (existingPCloudCloud != null && existingPCloudCloud.accessToken() != cloud.accessToken()) { + saveCloud(PCloudCloud.aCopyOf(existingPCloudCloud) // + .withUrl(cloud.url()) + .withAccessToken(cloud.accessToken()) + .build()) + } else if (existingPCloudCloud == null) { + saveCloud(cloud); + } + } + }) + } + + fun saveCloud(cloud: PCloudCloud) { + addOrChangeCloudConnectionUseCase // + .withCloud(cloud) // + .run(object : DefaultResultHandler() { + override fun onSuccess(void: Void?) { + loadCloudList() + } + }) + } + @Callback @RequiresApi(api = Build.VERSION_CODES.KITKAT) fun pickedLocalStorageLocation(result: ActivityResult) { diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt index b4f1af61..1d3411eb 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt @@ -2,6 +2,7 @@ package org.cryptomator.presentation.presenter import org.cryptomator.domain.Cloud import org.cryptomator.domain.LocalStorageCloud +import org.cryptomator.domain.PCloudCloud import org.cryptomator.domain.WebDavCloud import org.cryptomator.domain.di.PerView import org.cryptomator.domain.exception.FatalBackendException @@ -16,6 +17,7 @@ import org.cryptomator.presentation.intent.Intents import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel import org.cryptomator.presentation.model.LocalStorageModel +import org.cryptomator.presentation.model.PCloudCloudModel import org.cryptomator.presentation.model.WebDavCloudModel import org.cryptomator.presentation.model.mappers.CloudModelMapper import org.cryptomator.presentation.ui.activity.view.CloudSettingsView @@ -34,6 +36,7 @@ class CloudSettingsPresenter @Inject constructor( // private val nonSingleLoginClouds: Set = EnumSet.of( // CloudTypeModel.CRYPTO, // CloudTypeModel.LOCAL, // + CloudTypeModel.PCLOUD, // CloudTypeModel.WEBDAV) fun loadClouds() { @@ -41,7 +44,7 @@ class CloudSettingsPresenter @Inject constructor( // } fun onCloudClicked(cloudModel: CloudModel) { - if (isWebdavOrLocal(cloudModel)) { + if (isWebdavOrPCloudOrLocal(cloudModel)) { startConnectionListActivity(cloudModel.cloudType()) } else { if (isLoggedIn(cloudModel)) { @@ -58,8 +61,8 @@ class CloudSettingsPresenter @Inject constructor( // } } - private fun isWebdavOrLocal(cloudModel: CloudModel): Boolean { - return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel + private fun isWebdavOrPCloudOrLocal(cloudModel: CloudModel): Boolean { + return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel || cloudModel is PCloudCloudModel } private fun loginCloud(cloudModel: CloudModel) { @@ -91,6 +94,7 @@ class CloudSettingsPresenter @Inject constructor( // private fun effectiveTitle(cloudTypeModel: CloudTypeModel): String { when (cloudTypeModel) { CloudTypeModel.WEBDAV -> return context().getString(R.string.screen_cloud_settings_webdav_connections) + CloudTypeModel.PCLOUD -> return context().getString(R.string.screen_cloud_settings_pcloud_connections) CloudTypeModel.LOCAL -> return context().getString(R.string.screen_cloud_settings_local_storage_locations) } return context().getString(R.string.screen_cloud_settings_title) @@ -123,6 +127,7 @@ class CloudSettingsPresenter @Inject constructor( // .toMutableList() // .also { it.add(aWebdavCloud()) + it.add(aPCloudCloud()) it.add(aLocalCloud()) } view?.render(cloudModel) @@ -132,6 +137,10 @@ class CloudSettingsPresenter @Inject constructor( // return WebDavCloudModel(WebDavCloud.aWebDavCloudCloud().build()) } + private fun aPCloudCloud(): PCloudCloudModel { + return PCloudCloudModel(PCloudCloud.aPCloudCloud().build()) + } + private fun aLocalCloud(): CloudModel { return LocalStorageModel(LocalStorageCloud.aLocalStorage().build()) } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt index 1ddda74c..fbba49ce 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt @@ -7,6 +7,7 @@ import org.cryptomator.domain.exception.FatalBackendException import org.cryptomator.presentation.R import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.LocalStorageModel +import org.cryptomator.presentation.model.PCloudCloudModel import org.cryptomator.presentation.model.WebDavCloudModel import org.cryptomator.presentation.model.comparator.CloudModelComparator import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter.CloudConnectionHolder @@ -54,6 +55,8 @@ internal constructor(context: Context) : RecyclerViewBaseAdapterVorbereitungen zum Entsperren im Hintergrund WebDAV-Verbindungen + pCloud-Verbindungen Lokale Speicherorte Einloggen in Abmelden von diff --git a/presentation/src/main/res/values-es/strings.xml b/presentation/src/main/res/values-es/strings.xml index 7e55f17c..22b37eba 100644 --- a/presentation/src/main/res/values-es/strings.xml +++ b/presentation/src/main/res/values-es/strings.xml @@ -110,6 +110,7 @@ Versión Conexiones de WebDAV + Conexiones de pCloud Ubicaciones de almacenamiento local Iniciar sesión en Cerrar sesión de diff --git a/presentation/src/main/res/values-fr/strings.xml b/presentation/src/main/res/values-fr/strings.xml index a9fe6d4a..8dad8c28 100644 --- a/presentation/src/main/res/values-fr/strings.xml +++ b/presentation/src/main/res/values-fr/strings.xml @@ -173,6 +173,7 @@ Préparations du déverrouillage en arrière-plan Connexions WebDAV + Connexions pCloud Emplacements du stockage local Se connecter à Se déconnecter de diff --git a/presentation/src/main/res/values-tr/strings.xml b/presentation/src/main/res/values-tr/strings.xml index 4b24f543..40d13662 100644 --- a/presentation/src/main/res/values-tr/strings.xml +++ b/presentation/src/main/res/values-tr/strings.xml @@ -168,6 +168,7 @@ Arka planda kilit açma WebDAV bağlantıları + pCloud bağlantıları Yerel depolama konumları Giriş Oturumunu kapat diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index b7df4edb..67ef7c01 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -254,6 +254,7 @@ @string/screen_settings_cloud_settings_label WebDAV connections + pCloud connections Local storage locations Log in to Sign out from diff --git a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt index be9e4bee..e92f2bed 100644 --- a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt +++ b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt @@ -34,6 +34,7 @@ import org.cryptomator.presentation.exception.PermissionNotGrantedException import org.cryptomator.presentation.intent.AuthenticateCloudIntent import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel +import org.cryptomator.presentation.model.PCloudCloudModel import org.cryptomator.presentation.model.ProgressModel import org.cryptomator.presentation.model.ProgressStateModel import org.cryptomator.presentation.model.WebDavCloudModel @@ -65,6 +66,7 @@ class AuthenticateCloudPresenter @Inject constructor( // DropboxAuthStrategy(), // GoogleDriveAuthStrategy(), // OnedriveAuthStrategy(), // + PCloudAuthStrategy(), // WebDAVAuthStrategy(), // LocalStorageAuthStrategy() // ) @@ -282,6 +284,26 @@ class AuthenticateCloudPresenter @Inject constructor( // } } + private inner class PCloudAuthStrategy : AuthStrategy { + + override fun supports(cloud: CloudModel): Boolean { + return cloud.cloudType() == CloudTypeModel.PCLOUD + } + + override fun resumed(intent: AuthenticateCloudIntent) { + handlePCloudAuthenticationExceptionIfRequired(intent.cloud() as PCloudCloudModel, intent.error()) + } + + private fun handlePCloudAuthenticationExceptionIfRequired(cloud: PCloudCloudModel, e: AuthenticationException) { + Timber.tag("AuthicateCloudPrester").e(e) + when { + ExceptionUtil.contains(e, WrongCredentialsException::class.java) -> { + failAuthentication(cloud.name()) + } + } + } + } + private inner class WebDAVAuthStrategy : AuthStrategy { override fun supports(cloud: CloudModel): Boolean { From 9cc585eaf8edd80d857a76a30ce0902a0254ea51 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 15:01:38 +0100 Subject: [PATCH 34/93] fix: set pCloud predefined to false --- domain/src/main/java/org/cryptomator/domain/PCloudCloud.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java b/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java index 2518870a..718bbb32 100644 --- a/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/PCloudCloud.java @@ -57,7 +57,7 @@ public class PCloudCloud implements Cloud { @Override public boolean predefined() { - return true; + return false; } @Override From bd2bfa97244b57fe8f943a41b7ef09f990427651 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 15:02:59 +0100 Subject: [PATCH 35/93] feat: implement account removal for pCloud --- .../ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt index 363948b8..6f1e04cd 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/bottomsheet/CloudConnectionSettingsBottomSheet.kt @@ -7,6 +7,7 @@ import org.cryptomator.presentation.R import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel import org.cryptomator.presentation.model.LocalStorageModel +import org.cryptomator.presentation.model.PCloudCloudModel import org.cryptomator.presentation.model.WebDavCloudModel import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.change_cloud import kotlinx.android.synthetic.main.dialog_bottom_sheet_cloud_settings.delete_cloud @@ -28,6 +29,7 @@ class CloudConnectionSettingsBottomSheet : BaseBottomSheet bindViewForWebDAV(cloudModel as WebDavCloudModel) + CloudTypeModel.PCLOUD -> bindViewForPCloud(cloudModel as PCloudCloudModel) CloudTypeModel.LOCAL -> bindViewForLocal(cloudModel as LocalStorageModel) else -> throw IllegalStateException("Cloud model is not binded in the view") } @@ -59,6 +61,11 @@ class CloudConnectionSettingsBottomSheet : BaseBottomSheet Date: Tue, 16 Mar 2021 15:07:00 +0100 Subject: [PATCH 36/93] chore: simplify check if account was already registered --- .../presenter/CloudConnectionListPresenter.kt | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index da62bc5c..96799a22 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -204,9 +204,6 @@ class CloudConnectionListPresenter @Inject constructor( // prepareForSavingPCloudCloud(PCloudCloud.aCopyOf(pCloudSkeleton).withUsername(username).build()) } }) - - Log.d("pCloud", "Account access granted, authData:\n$authData") - } AuthorizationResult.ACCESS_DENIED -> //TODO: Add proper handling for denied grants. Log.d("pCloud", "Account access denied") @@ -226,20 +223,14 @@ class CloudConnectionListPresenter @Inject constructor( // .withCloudType(CloudTypeModel.valueOf(selectedCloudType.get())) // .run(object : DefaultResultHandler>() { override fun onSuccess(clouds: List) { - // here check if a cloud with the same mail adress already exists, - // if so update (in case of the token changed) else create a new one - val existingPCloudCloud: PCloudCloud? = clouds.firstOrNull { + clouds.firstOrNull { (it as PCloudCloud).username() == cloud.username() - } as PCloudCloud? - - if (existingPCloudCloud != null && existingPCloudCloud.accessToken() != cloud.accessToken()) { - saveCloud(PCloudCloud.aCopyOf(existingPCloudCloud) // + }?.let { it as PCloudCloud + saveCloud(PCloudCloud.aCopyOf(it) // .withUrl(cloud.url()) - .withAccessToken(cloud.accessToken()) + .withAccessToken(it.accessToken()) .build()) - } else if (existingPCloudCloud == null) { - saveCloud(cloud); - } + } ?: saveCloud(cloud) } }) } From 05b95cf5da238e01333d80f0d24296ada853957b Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 15:22:55 +0100 Subject: [PATCH 37/93] fix: folder listing --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 83251861..42d53c43 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -125,7 +125,9 @@ class PCloudImpl { public List list(CloudFolder folder) throws ApiError, IOException { List result = new ArrayList<>(); - RemoteFolder listFolderResult = null; + RemoteFolder listFolderResult = client() // + .listFolder(((PCloudFolder) folder).getId()) // + .execute(); List entryMetadata = listFolderResult.children(); for (RemoteEntry metadata : entryMetadata) { result.add(PCloudCloudNodeFactory.from( // From 85d920da19ad2d831107db80ca2cbcc741cb6368 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 15:23:37 +0100 Subject: [PATCH 38/93] fix: instatiate RootPCloudFolder with path / --- .../org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java index 60fc42e2..40def759 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java @@ -9,7 +9,7 @@ class RootPCloudFolder extends PCloudFolder { private static final long rootFolderId = 0L; public RootPCloudFolder(PCloudCloud cloud) { - super(null, rootFolderId, "", ""); + super(null, rootFolderId, "", "/"); this.cloud = cloud; } From c29801f132bc3799b30e3a2986b2d162713662bd Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 15:40:04 +0100 Subject: [PATCH 39/93] Revert "fix: instatiate RootPCloudFolder with path /" This reverts commit c93a6b289de6817f693fe00ef1f47130baeca4c6. --- .../org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java index 40def759..60fc42e2 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java @@ -9,7 +9,7 @@ class RootPCloudFolder extends PCloudFolder { private static final long rootFolderId = 0L; public RootPCloudFolder(PCloudCloud cloud) { - super(null, rootFolderId, "", "/"); + super(null, rootFolderId, "", ""); this.cloud = cloud; } From ceb2de61d4c8e515a3b251c03f86d005eb69a682 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 15:54:01 +0100 Subject: [PATCH 40/93] fix: make fileid null safe --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 42d53c43..86cb0d97 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -205,7 +205,12 @@ class PCloudImpl { public void read(CloudFile file, OutputStream data, final ProgressAware progressAware) throws ApiError, IOException { progressAware.onProgress(Progress.started(DownloadState.download(file))); - FileLink fileLink = client().createFileLink(((PCloudFile) file).getId(), DownloadOptions.DEFAULT).execute(); + Long fileId = ((PCloudFile)file).getId(); + if (fileId == null) { + fileId = client().stat(file.getPath()).execute().fileId(); + } + + FileLink fileLink = client().createFileLink(fileId, DownloadOptions.DEFAULT).execute(); ProgressListener listener = (done, total) -> progressAware.onProgress( // progress(DownloadState.download(file)) // From 42abae3a5a91d9b9df701a1e2b710d1885b68dce Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 16:34:20 +0100 Subject: [PATCH 41/93] fix: read --- .../main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 86cb0d97..d7277006 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -221,7 +221,7 @@ class PCloudImpl { DataSink sink = new DataSink() { @Override public void readAll(BufferedSource source) throws IOException { - CopyStream.copyStreamToStream(source.getBuffer().inputStream(), data); + CopyStream.copyStreamToStream(source.inputStream(), data); } }; From ad8d5338caeeaa7ffd2f5897bcb34cd083df5e18 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 16:42:49 +0100 Subject: [PATCH 42/93] fix: exist to also check files properly --- .../data/cloud/pcloud/PCloudApiErrorCodes.java | 3 ++- .../org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java index f444349f..9985ea0b 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java @@ -9,7 +9,7 @@ public enum PCloudApiErrorCodes { INVALID_DESTINATION(1037), PROVIDE_URL(1040), LOGIN_FAILED(2000), - INVALID_FILE_FOLDER_NAME(2001), + INVALID_FILE_OR_FOLDER_NAME(2001), COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST(2002), ACCESS_DENIED(2003), FILE_OR_FOLDER_ALREADY_EXISTS(2004), @@ -22,6 +22,7 @@ public enum PCloudApiErrorCodes { ACTIVE_SHARES_OR_SHAREREQUESTS_PRESENT(2028), CANNOT_RENAME_ROOT_FOLDER(2042), CANNOT_MOVE_FOLDER_INTO_SUBFOLDER_OF_ITSELF(2043), + FILE_OR_FOLDER_NOT_FOUND(2055), INVALID_ACCESS_TOKEN(2094), TOO_MANY_LOGIN_TRIES_FROM_IP(4000), INTERNAL_ERROR(5000), diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index d7277006..0153bdb1 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -43,10 +43,6 @@ import okio.Source; import static org.cryptomator.domain.usecases.cloud.Progress.progress; class PCloudImpl { - - private static final int DIRECTORY_DOES_NOT_EXIST = 2005; - private static final int INVALID_FILE_OR_FOLDER_NAME = 2001; - private final PCloudClientFactory clientFactory = new PCloudClientFactory(); private final PCloudCloud cloud; private final RootPCloudFolder root; @@ -116,7 +112,9 @@ class PCloudImpl { return true; } } catch (ApiError e) { - if (e.errorCode() == DIRECTORY_DOES_NOT_EXIST || e.errorCode() == INVALID_FILE_OR_FOLDER_NAME) { + if (e.errorCode() == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() + || e.errorCode() == PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue() + || e.errorCode() == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()) { return false; } throw e; From b08a9cb5498f360d7857b9b93252db56c55f9b36 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 17:34:01 +0100 Subject: [PATCH 43/93] fix: override contentLength to return size of data --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 0153bdb1..480eb8e0 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -187,6 +187,11 @@ class PCloudImpl { .withValue(done)); com.pcloud.sdk.DataSource pCloudDataSource = new com.pcloud.sdk.DataSource() { + @Override + public long contentLength() { + return data.size(context).get(); + } + @Override public void writeTo(BufferedSink sink) throws IOException { try (Source source = Okio.source(data.open(context))) { @@ -196,7 +201,7 @@ class PCloudImpl { }; return client() // - .createFile(((PCloudFolder) file.getParent()).getId(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // + .createFile(file.getParent().getId(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // .execute(); } From 7dc9456d972030626c9a189f8be2a9de3ec38f6d Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 17:39:41 +0100 Subject: [PATCH 44/93] fix: null safe folderid for list --- .../cryptomator/data/cloud/pcloud/PCloudImpl.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 480eb8e0..58f9fd17 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -123,9 +123,17 @@ class PCloudImpl { public List list(CloudFolder folder) throws ApiError, IOException { List result = new ArrayList<>(); - RemoteFolder listFolderResult = client() // - .listFolder(((PCloudFolder) folder).getId()) // - .execute(); + + Long folderId = ((PCloudFolder)folder).getId(); + RemoteFolder listFolderResult; + if (folderId == null) { + listFolderResult = client().listFolder(folder.getPath()).execute(); + } else { + listFolderResult = client() // + .listFolder(((PCloudFolder) folder).getId()) // + .execute(); + } + List entryMetadata = listFolderResult.children(); for (RemoteEntry metadata : entryMetadata) { result.add(PCloudCloudNodeFactory.from( // From 81ee67b378a61e14bd802da2980bf4777af675c4 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 21:12:56 +0100 Subject: [PATCH 45/93] feat: introduce idCache for pCloud --- .../pcloud/PCloudCloudContentRepository.java | 14 +- .../PCloudCloudContentRepositoryFactory.java | 6 +- .../cloud/pcloud/PCloudCloudNodeFactory.java | 31 +++- .../data/cloud/pcloud/PCloudIdCache.java | 77 +++++++++ .../data/cloud/pcloud/PCloudIdCloudNode.java | 9 + .../data/cloud/pcloud/PCloudImpl.java | 163 +++++++++++++----- .../data/cloud/pcloud/PCloudNode.java | 6 +- 7 files changed, 242 insertions(+), 64 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java index 0f5f6190..3b311232 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java @@ -30,8 +30,8 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository size) { + return new PCloudFile(parent, null, name, getNodePath(parent, name), size, Optional.empty()); } - private static String getNodePath(PCloudFolder parent, String name) { - return parent.getPath() + "/" + name; + public static PCloudFile file(PCloudFolder folder, String name, Optional size, String path, Long fileId) { + return new PCloudFile(folder, fileId, name, path, size, Optional.empty()); + } + + public static PCloudFolder folder(PCloudFolder parent, RemoteFolder metadata) { + return new PCloudFolder(parent, metadata.folderId(), metadata.name(), getNodePath(parent, metadata.name())); } public static PCloudFolder folder(PCloudFolder parent, String name, String path) { return new PCloudFolder(parent, null, name, path); } + public static PCloudFolder folder(PCloudFolder parent, String name) { + return new PCloudFolder(parent, null, name, getNodePath(parent, name)); + } + + public static PCloudFolder folder(PCloudFolder parent, String name, String path, Long folderId) { + return new PCloudFolder(parent, folderId, name, path); + } + + public static String getNodePath(PCloudFolder parent, String name) { + return parent.getPath() + "/" + name; + } + public static PCloudNode from(PCloudFolder parent, RemoteEntry metadata) { if (metadata instanceof RemoteFile) { - return from(parent, (RemoteFile) metadata); + return file(parent, metadata.asFile()); } else { - return from(parent, (RemoteFolder) metadata); + return folder(parent, metadata.asFolder()); } } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java new file mode 100644 index 00000000..7c22b0cd --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java @@ -0,0 +1,77 @@ +package org.cryptomator.data.cloud.pcloud; + +import android.util.LruCache; + +import org.cryptomator.domain.CloudFolder; + +import javax.inject.Inject; + +class PCloudIdCache { + + private final LruCache cache; + + @Inject + PCloudIdCache() { + cache = new LruCache<>(1000); + } + + public NodeInfo get(String path) { + return cache.get(path); + } + + T cache(T value) { + add(value); + return value; + } + + public void add(PCloudIdCloudNode node) { + add(node.getPath(), new NodeInfo(node)); + } + + private void add(String path, NodeInfo info) { + cache.put(path, info); + } + + public void remove(PCloudIdCloudNode node) { + remove(node.getPath()); + } + + private void remove(String path) { + removeChildren(path); + cache.remove(path); + } + + private void removeChildren(String path) { + String prefix = path + '/'; + for (String key : cache.snapshot().keySet()) { + if (key.startsWith(prefix)) { + cache.remove(key); + } + } + } + + static class NodeInfo { + + private final Long id; + private final boolean isFolder; + + private NodeInfo(PCloudIdCloudNode node) { + this(node.getId(), node instanceof CloudFolder); + } + + NodeInfo(Long id, boolean isFolder) { + this.id = id; + this.isFolder = isFolder; + } + + public Long getId() { + return id; + } + + public boolean isFolder() { + return isFolder; + } + + } + +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java new file mode 100644 index 00000000..3490cb53 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java @@ -0,0 +1,9 @@ +package org.cryptomator.data.cloud.pcloud; + +import org.cryptomator.domain.CloudNode; + +interface PCloudIdCloudNode extends CloudNode { + + Long getId(); + +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 58f9fd17..695c77c5 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -16,10 +16,11 @@ import com.pcloud.sdk.UserInfo; import org.cryptomator.data.util.CopyStream; import org.cryptomator.domain.CloudFile; -import org.cryptomator.domain.CloudFolder; import org.cryptomator.domain.CloudNode; import org.cryptomator.domain.PCloudCloud; +import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; +import org.cryptomator.domain.exception.NoSuchCloudFileException; import org.cryptomator.domain.exception.authentication.NoAuthenticationProvidedException; import org.cryptomator.domain.usecases.ProgressAware; import org.cryptomator.domain.usecases.cloud.DataSource; @@ -43,16 +44,20 @@ import okio.Source; import static org.cryptomator.domain.usecases.cloud.Progress.progress; class PCloudImpl { + + private final PCloudIdCache idCache; + private final PCloudClientFactory clientFactory = new PCloudClientFactory(); private final PCloudCloud cloud; private final RootPCloudFolder root; private final Context context; - PCloudImpl(PCloudCloud cloud, Context context) { + PCloudImpl(PCloudCloud cloud, Context context, PCloudIdCache idCache) { if (cloud.accessToken() == null) { throw new NoAuthenticationProvidedException(cloud); } this.cloud = cloud; + this.idCache = idCache; this.root = new RootPCloudFolder(cloud); this.context = context; } @@ -83,36 +88,87 @@ class PCloudImpl { return folder; } - public PCloudFile file(CloudFolder folder, String name) { - return file(folder, name, Optional.empty()); + public PCloudFile file(PCloudFolder parent, String name) { + return file(parent, name, Optional.empty()); } - public PCloudFile file(CloudFolder folder, String name, Optional size) { + public PCloudFile file(PCloudFolder parent, String name, Optional size) { + if (parent.getId() == null) { + return PCloudCloudNodeFactory.file(parent, name, size); + } + String path = PCloudCloudNodeFactory.getNodePath(parent, name); + PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); + if (nodeInfo != null && !nodeInfo.isFolder()) { + return PCloudCloudNodeFactory.file(parent, name, size, path, nodeInfo.getId()); + } + + Optional file = findEntry(parent.getId(), name, false); + if (file.isPresent()) { + return idCache.cache(PCloudCloudNodeFactory.file(parent, file.get().asFile())); + } + return PCloudCloudNodeFactory.file( // - (PCloudFolder) folder, // + parent, // name, // size, // - folder.getPath() + '/' + name); + parent.getPath() + '/' + name); } - public PCloudFolder folder(CloudFolder folder, String name) { - return PCloudCloudNodeFactory.folder( // - (PCloudFolder) folder, // - name, // - folder.getPath() + '/' + name); + public PCloudFolder folder(PCloudFolder parent, String name) { + if (parent.getId() == null) { + return PCloudCloudNodeFactory.folder(parent, name); + } + String path = PCloudCloudNodeFactory.getNodePath(parent, name); + PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); + if (nodeInfo != null && nodeInfo.isFolder()) { + return PCloudCloudNodeFactory.folder( // + parent, // + name, // + path, // + nodeInfo.getId()); + } + + Optional folder = findEntry(parent.getId(), name, true); + if (folder.isPresent()) { + return idCache.cache(PCloudCloudNodeFactory.folder(parent, folder.get().asFolder())); + } + return PCloudCloudNodeFactory.folder(parent, name, parent.getPath() + '/' + name); } - public boolean exists(CloudNode node) throws ApiError, IOException { + private Optional findEntry(Long folderId, String name, boolean isFolder) { + try { + RemoteFolder remoteFolder = client().listFolder(folderId).execute(); + for (RemoteEntry remoteEntry : remoteFolder.children()) { + if (isFolder) { + if (remoteEntry.isFolder() && remoteEntry.name().equals(name)) { + return Optional.of(remoteEntry); + } + } else { + if (remoteEntry.isFile() && remoteEntry.name().equals(name)) { + return Optional.of(remoteEntry); + } + } + } + return Optional.empty(); + } catch(ApiError | IOException ex) { + return Optional.empty(); + } + } + + public boolean exists(PCloudNode node) throws ApiError, IOException { try { if (node instanceof PCloudFolder) { - client().listFolder(((PCloudFolder) node).getPath()).execute(); + RemoteFolder remoteFolder = client().listFolder(node.getPath()).execute(); + idCache.add(PCloudCloudNodeFactory.folder(node.getParent(), remoteFolder)); return true; } else { - client().stat(((PCloudFile)node).getPath()).execute(); + RemoteFile remoteFile = client().stat(node.getPath()).execute(); + idCache.add(PCloudCloudNodeFactory.file(node.getParent(), remoteFile)); return true; } } catch (ApiError e) { if (e.errorCode() == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() + || e.errorCode() == PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue() || e.errorCode() == PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue() || e.errorCode() == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()) { return false; @@ -121,56 +177,60 @@ class PCloudImpl { } } - public List list(CloudFolder folder) throws ApiError, IOException { + public List list(PCloudFolder folder) throws ApiError, IOException { List result = new ArrayList<>(); - Long folderId = ((PCloudFolder)folder).getId(); + Long folderId = folder.getId(); RemoteFolder listFolderResult; if (folderId == null) { listFolderResult = client().listFolder(folder.getPath()).execute(); } else { listFolderResult = client() // - .listFolder(((PCloudFolder) folder).getId()) // + .listFolder(folder.getId()) // .execute(); } List entryMetadata = listFolderResult.children(); for (RemoteEntry metadata : entryMetadata) { - result.add(PCloudCloudNodeFactory.from( // - (PCloudFolder) folder, // - metadata)); + result.add(PCloudCloudNodeFactory.from(folder, metadata)); } return result; } - public PCloudFolder create(CloudFolder folder) throws ApiError, IOException { - RemoteFolder createFolderResult = client() // - .createFolder(((PCloudFolder)folder.getParent()).getId(), folder.getName()) // + public PCloudFolder create(PCloudFolder folder) throws ApiError, IOException { + RemoteFolder createdFolder = client() // + .createFolder(folder.getParent().getId(), folder.getName()) // .execute(); - - return PCloudCloudNodeFactory.from( // - (PCloudFolder) folder.getParent(), // - createFolderResult.asFolder()); + return idCache.cache( // + PCloudCloudNodeFactory.folder(folder.getParent(), createdFolder)); } - public CloudNode move(CloudNode source, CloudNode target) throws ApiError, IOException { + public CloudNode move(PCloudNode source, PCloudNode target) throws ApiError, BackendException, IOException { RemoteEntry relocationResult; - if (source instanceof PCloudFolder) { - relocationResult = client().moveFolder(((PCloudFolder) source).getId(), ((PCloudFolder) target).getId()).execute(); - } else { - relocationResult = client().moveFile(((PCloudFile) source).getId(), ((PCloudFolder) target).getId()).execute(); + if (exists(target)) { + throw new CloudNodeAlreadyExistsException(target.getName()); } - return PCloudCloudNodeFactory.from( // - (PCloudFolder) target.getParent(), // - relocationResult); + if (source instanceof PCloudFolder) { + relocationResult = client().moveFolder(source.getId(), target.getId()).execute(); + } else { + relocationResult = client().moveFile(source.getId(), target.getId()).execute(); + } + + idCache.remove(source); + return PCloudCloudNodeFactory.from(target.getParent(), relocationResult); } - public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) throws ApiError, IOException, CloudNodeAlreadyExistsException { + public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) + throws ApiError, BackendException, IOException { if (exists(file) && !replace) { throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false"); } + if (file.getParent().getId() == null) { + throw new NoSuchCloudFileException(String.format("The parent folder of %s doesn't have a folderId. The file would remain in root folder", file.getPath())); + } + progressAware.onProgress(Progress.started(UploadState.upload(file))); UploadOptions uploadOptions = UploadOptions.DEFAULT; if (replace) { @@ -181,9 +241,7 @@ class PCloudImpl { progressAware.onProgress(Progress.completed(UploadState.upload(file))); - return PCloudCloudNodeFactory.from( // - file.getParent(), // - uploadedFile); + return idCache.cache(PCloudCloudNodeFactory.file(file.getParent(), uploadedFile)); } private RemoteFile uploadFile(final PCloudFile file, DataSource data, final ProgressAware progressAware, UploadOptions uploadOptions, final long size) // @@ -208,17 +266,30 @@ class PCloudImpl { } }; + Long parentFolderId = file.getParent().getId(); + if (parentFolderId == null) { + parentFolderId = idCache.get(file.getParent().getPath()).getId(); + } + return client() // - .createFile(file.getParent().getId(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // + .createFile(parentFolderId, file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // .execute(); } +// private Long getFolderId(PCloudFolder folder) { +// try { +// return client().listFolder(folder.getPath()).execute().folderId(); +// } catch(ApiError | IOException e) { +// return null; +// } +// } + public void read(CloudFile file, OutputStream data, final ProgressAware progressAware) throws ApiError, IOException { progressAware.onProgress(Progress.started(DownloadState.download(file))); Long fileId = ((PCloudFile)file).getId(); if (fileId == null) { - fileId = client().stat(file.getPath()).execute().fileId(); + fileId = idCache.get(file.getPath()).getId(); } FileLink fileLink = client().createFileLink(fileId, DownloadOptions.DEFAULT).execute(); @@ -241,15 +312,15 @@ class PCloudImpl { progressAware.onProgress(Progress.completed(DownloadState.download(file))); } - public void delete(CloudNode node) throws ApiError, IOException { + public void delete(PCloudNode node) throws ApiError, IOException { if (node instanceof PCloudFolder) { client() // - .deleteFolder(((PCloudFolder) node).getId()).execute(); + .deleteFolder(node.getId()).execute(); } else { client() // - .deleteFile(((PCloudFile) node).getId()).execute(); + .deleteFile(node.getId()).execute(); } - + idCache.remove(node); } public String currentAccount() throws ApiError, IOException { diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java index c9b03334..af04d938 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java @@ -2,8 +2,12 @@ package org.cryptomator.data.cloud.pcloud; import org.cryptomator.domain.CloudNode; -interface PCloudNode extends CloudNode { +interface PCloudNode extends PCloudIdCloudNode { + @Override Long getId(); + @Override + PCloudFolder getParent(); + } From 8f145735ed2514de0cdc1bfca6a1e27c190e6213 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 22:30:26 +0100 Subject: [PATCH 46/93] fix: missing IDs in cache --- .../pcloud/PCloudCloudContentRepository.java | 9 +- .../data/cloud/pcloud/PCloudImpl.java | 126 +++++++++--------- 2 files changed, 66 insertions(+), 69 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java index 3b311232..a78dfbee 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java @@ -5,6 +5,7 @@ import android.content.Context; import com.pcloud.sdk.ApiError; import org.cryptomator.data.cloud.InterceptingCloudContentRepository; +import org.cryptomator.domain.CloudNode; import org.cryptomator.domain.PCloudCloud; import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; @@ -95,7 +96,7 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository list(PCloudFolder folder) throws BackendException { + public List list(PCloudFolder folder) throws BackendException { try { return cloud.list(folder); } catch (ApiError | IOException e) { @@ -184,8 +185,10 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository size) { - if (parent.getId() == null) { - return PCloudCloudNodeFactory.file(parent, name, size); - } - String path = PCloudCloudNodeFactory.getNodePath(parent, name); - PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); - if (nodeInfo != null && !nodeInfo.isFolder()) { - return PCloudCloudNodeFactory.file(parent, name, size, path, nodeInfo.getId()); - } - - Optional file = findEntry(parent.getId(), name, false); - if (file.isPresent()) { - return idCache.cache(PCloudCloudNodeFactory.file(parent, file.get().asFile())); - } - - return PCloudCloudNodeFactory.file( // - parent, // - name, // - size, // - parent.getPath() + '/' + name); - } - - public PCloudFolder folder(PCloudFolder parent, String name) { - if (parent.getId() == null) { - return PCloudCloudNodeFactory.folder(parent, name); - } - String path = PCloudCloudNodeFactory.getNodePath(parent, name); - PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); - if (nodeInfo != null && nodeInfo.isFolder()) { - return PCloudCloudNodeFactory.folder( // - parent, // - name, // - path, // - nodeInfo.getId()); - } - - Optional folder = findEntry(parent.getId(), name, true); - if (folder.isPresent()) { - return idCache.cache(PCloudCloudNodeFactory.folder(parent, folder.get().asFolder())); - } - return PCloudCloudNodeFactory.folder(parent, name, parent.getPath() + '/' + name); - } - private Optional findEntry(Long folderId, String name, boolean isFolder) { try { RemoteFolder remoteFolder = client().listFolder(folderId).execute(); @@ -155,17 +109,57 @@ class PCloudImpl { } } + public PCloudFile file(PCloudFolder parent, String name) { + return file(parent, name, Optional.empty()); + } + + public PCloudFile file(PCloudFolder parent, String name, Optional size) { + if (parent.getId() == null) { + return PCloudCloudNodeFactory.file(parent, name, size); + } + + String path = PCloudCloudNodeFactory.getNodePath(parent, name); + PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); + if (nodeInfo != null && !nodeInfo.isFolder()) { + return PCloudCloudNodeFactory.file(parent, name, size, path, nodeInfo.getId()); + } + + Optional file = findEntry(parent.getId(), name, false); + if (file.isPresent()) { + return idCache.cache(PCloudCloudNodeFactory.file(parent, file.get().asFile())); + } + + return PCloudCloudNodeFactory.file(parent, name, size, parent.getPath() + '/' + name); + } + + public PCloudFolder folder(PCloudFolder parent, String name) { + if (parent.getId() == null) { + return PCloudCloudNodeFactory.folder(parent, name); + } + + String path = PCloudCloudNodeFactory.getNodePath(parent, name); + PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); + if (nodeInfo != null && nodeInfo.isFolder()) { + return PCloudCloudNodeFactory.folder(parent, name, path, nodeInfo.getId()); + } + + Optional folder = findEntry(parent.getId(), name, true); + if (folder.isPresent()) { + return idCache.cache(PCloudCloudNodeFactory.folder(parent, folder.get().asFolder())); + } + return PCloudCloudNodeFactory.folder(parent, name, parent.getPath() + '/' + name); + } + public boolean exists(PCloudNode node) throws ApiError, IOException { try { if (node instanceof PCloudFolder) { RemoteFolder remoteFolder = client().listFolder(node.getPath()).execute(); idCache.add(PCloudCloudNodeFactory.folder(node.getParent(), remoteFolder)); - return true; } else { RemoteFile remoteFile = client().stat(node.getPath()).execute(); idCache.add(PCloudCloudNodeFactory.file(node.getParent(), remoteFile)); - return true; } + return true; } catch (ApiError e) { if (e.errorCode() == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() || e.errorCode() == PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue() @@ -177,8 +171,8 @@ class PCloudImpl { } } - public List list(PCloudFolder folder) throws ApiError, IOException { - List result = new ArrayList<>(); + public List list(PCloudFolder folder) throws ApiError, IOException { + List result = new ArrayList<>(); Long folderId = folder.getId(); RemoteFolder listFolderResult; @@ -192,12 +186,20 @@ class PCloudImpl { List entryMetadata = listFolderResult.children(); for (RemoteEntry metadata : entryMetadata) { - result.add(PCloudCloudNodeFactory.from(folder, metadata)); + result.add(idCache.cache(PCloudCloudNodeFactory.from(folder, metadata))); } return result; } public PCloudFolder create(PCloudFolder folder) throws ApiError, IOException { + if (folder.getParent().getId() == null) { + folder = new PCloudFolder( // + create(folder.getParent()), // + folder.getId(), // + folder.getName(), // + folder.getPath()); + } + RemoteFolder createdFolder = client() // .createFolder(folder.getParent().getId(), folder.getName()) // .execute(); @@ -212,13 +214,13 @@ class PCloudImpl { } if (source instanceof PCloudFolder) { - relocationResult = client().moveFolder(source.getId(), target.getId()).execute(); + relocationResult = client().moveFolder(source.getId(), target.getParent().getId()).execute(); } else { - relocationResult = client().moveFile(source.getId(), target.getId()).execute(); + relocationResult = client().moveFile(source.getId(), target.getParent().getId()).execute(); } idCache.remove(source); - return PCloudCloudNodeFactory.from(target.getParent(), relocationResult); + return idCache.cache(PCloudCloudNodeFactory.from(target.getParent(), relocationResult)); } public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) @@ -233,7 +235,7 @@ class PCloudImpl { progressAware.onProgress(Progress.started(UploadState.upload(file))); UploadOptions uploadOptions = UploadOptions.DEFAULT; - if (replace) { + if (file.getId() != null && replace) { uploadOptions = UploadOptions.OVERRIDE_FILE; } @@ -276,14 +278,6 @@ class PCloudImpl { .execute(); } -// private Long getFolderId(PCloudFolder folder) { -// try { -// return client().listFolder(folder.getPath()).execute().folderId(); -// } catch(ApiError | IOException e) { -// return null; -// } -// } - public void read(CloudFile file, OutputStream data, final ProgressAware progressAware) throws ApiError, IOException { progressAware.onProgress(Progress.started(DownloadState.download(file))); @@ -315,7 +309,7 @@ class PCloudImpl { public void delete(PCloudNode node) throws ApiError, IOException { if (node instanceof PCloudFolder) { client() // - .deleteFolder(node.getId()).execute(); + .deleteFolder(node.getId(), true).execute(); } else { client() // .deleteFile(node.getId()).execute(); From 46dbde1103b26b6256278569bbcb81e7b358429c Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 22:49:10 +0100 Subject: [PATCH 47/93] fix(move): rename file after relocation --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index a38f93c5..f8a9ed89 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -214,9 +214,12 @@ class PCloudImpl { } if (source instanceof PCloudFolder) { + //TODO: check if this can be reached at all relocationResult = client().moveFolder(source.getId(), target.getParent().getId()).execute(); + relocationResult = client().renameFolder(relocationResult.asFolder(), target.getName()).execute(); } else { - relocationResult = client().moveFile(source.getId(), target.getParent().getId()).execute(); + relocationResult = client().moveFile(source.getId(), target.getParent().getId()).execute().asFile(); + relocationResult = client().renameFile(relocationResult.asFile(), target.getName()).execute(); } idCache.remove(source); From 615fe5558e5edea8e4b03c4b983a0cf33b035328 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 07:04:57 +0100 Subject: [PATCH 48/93] fix: move() only rename if relocationResult has different name than target --- .../org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index f8a9ed89..c1dd58c5 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -214,12 +214,15 @@ class PCloudImpl { } if (source instanceof PCloudFolder) { - //TODO: check if this can be reached at all relocationResult = client().moveFolder(source.getId(), target.getParent().getId()).execute(); - relocationResult = client().renameFolder(relocationResult.asFolder(), target.getName()).execute(); + if (!relocationResult.name().equals(target.getName())) { + relocationResult = client().renameFolder(relocationResult.asFolder(), target.getName()).execute(); + } } else { - relocationResult = client().moveFile(source.getId(), target.getParent().getId()).execute().asFile(); - relocationResult = client().renameFile(relocationResult.asFile(), target.getName()).execute(); + relocationResult = client().moveFile(source.getId(), target.getParent().getId()).execute(); + if (!relocationResult.name().equals(target.getName())) { + relocationResult = client().renameFile(relocationResult.asFile(), target.getName()).execute(); + } } idCache.remove(source); From c958558f50e6f4f067ad818093404fdb126c09bc Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 07:29:01 +0100 Subject: [PATCH 49/93] fix: change signatures, remove unneccessary methods - Change signatures of PCloudImpl, PCloudFile and PCloudFolder to match Google Drive implementation - Remove unneccessary file() and folder() methods (read path directly) --- .../pcloud/PCloudCloudContentRepository.java | 2 +- .../cloud/pcloud/PCloudCloudNodeFactory.java | 35 ++++++++----------- .../data/cloud/pcloud/PCloudFile.java | 16 ++++----- .../data/cloud/pcloud/PCloudFolder.java | 18 +++++----- .../data/cloud/pcloud/PCloudImpl.java | 12 +++---- .../data/cloud/pcloud/RootPCloudFolder.java | 2 +- 6 files changed, 40 insertions(+), 45 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java index a78dfbee..d83a4bef 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java @@ -59,7 +59,7 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository size, String path) { - return new PCloudFile(parent, null, name, path, size, Optional.empty()); + public static PCloudFile file(PCloudFolder parent, RemoteFile file) { + return new PCloudFile(parent, file.name(), getNodePath(parent, file.name()), file.fileId(), Optional.ofNullable(file.size()), Optional.ofNullable(file.lastModified())); } public static PCloudFile file(PCloudFolder parent, String name, Optional size) { - return new PCloudFile(parent, null, name, getNodePath(parent, name), size, Optional.empty()); + return new PCloudFile(parent, name, getNodePath(parent, name), null, size, Optional.empty()); } public static PCloudFile file(PCloudFolder folder, String name, Optional size, String path, Long fileId) { - return new PCloudFile(folder, fileId, name, path, size, Optional.empty()); + return new PCloudFile(folder, name, path, fileId, size, Optional.empty()); } - public static PCloudFolder folder(PCloudFolder parent, RemoteFolder metadata) { - return new PCloudFolder(parent, metadata.folderId(), metadata.name(), getNodePath(parent, metadata.name())); - } - - public static PCloudFolder folder(PCloudFolder parent, String name, String path) { - return new PCloudFolder(parent, null, name, path); + public static PCloudFolder folder(PCloudFolder parent, RemoteFolder folder) { + return new PCloudFolder(parent, folder.name(), getNodePath(parent, folder.name()), folder.folderId()); } public static PCloudFolder folder(PCloudFolder parent, String name) { - return new PCloudFolder(parent, null, name, getNodePath(parent, name)); + return new PCloudFolder(parent, name, getNodePath(parent, name), null); } public static PCloudFolder folder(PCloudFolder parent, String name, String path, Long folderId) { - return new PCloudFolder(parent, folderId, name, path); + return new PCloudFolder(parent, name, path, folderId); } public static String getNodePath(PCloudFolder parent, String name) { return parent.getPath() + "/" + name; } - public static PCloudNode from(PCloudFolder parent, RemoteEntry metadata) { - if (metadata instanceof RemoteFile) { - return file(parent, metadata.asFile()); + public static PCloudNode from(PCloudFolder parent, RemoteEntry remoteEntry) { + if (remoteEntry instanceof RemoteFile) { + return file(parent, remoteEntry.asFile()); } else { - return folder(parent, metadata.asFolder()); + return folder(parent, remoteEntry.asFolder()); } } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java index efca898f..787baa93 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java @@ -9,17 +9,17 @@ import java.util.Date; class PCloudFile implements CloudFile, PCloudNode { private final PCloudFolder parent; - private final Long fileid; private final String name; private final String path; + private final Long fileId; private final Optional size; private final Optional modified; - public PCloudFile(PCloudFolder parent, Long fileid, String name, String path, Optional size, Optional modified) { + public PCloudFile(PCloudFolder parent, String name, String path, Long fileId, Optional size, Optional modified) { this.parent = parent; - this.fileid = fileid; this.name = name; this.path = path; + this.fileId = fileId; this.size = size; this.modified = modified; } @@ -29,11 +29,6 @@ class PCloudFile implements CloudFile, PCloudNode { return parent.getCloud(); } - @Override - public Long getId() { - return fileid; - } - @Override public String getName() { return name; @@ -44,6 +39,11 @@ class PCloudFile implements CloudFile, PCloudNode { return path; } + @Override + public Long getId() { + return fileId; + } + @Override public PCloudFolder getParent() { return parent; diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java index fc57bb0d..7abc0496 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java @@ -6,13 +6,13 @@ import org.cryptomator.domain.CloudFolder; class PCloudFolder implements CloudFolder, PCloudNode { private final PCloudFolder parent; - private final Long folderid; private final String name; private final String path; + private final Long folderId; - public PCloudFolder(PCloudFolder parent, Long folderid, String name, String path) { + public PCloudFolder(PCloudFolder parent, String name, String path, Long folderId) { this.parent = parent; - this.folderid = folderid; + this.folderId = folderId; this.name = name; this.path = path; } @@ -22,11 +22,6 @@ class PCloudFolder implements CloudFolder, PCloudNode { return parent.getCloud(); } - @Override - public Long getId() { - return folderid; - } - @Override public String getName() { return name; @@ -37,6 +32,11 @@ class PCloudFolder implements CloudFolder, PCloudNode { return path; } + @Override + public Long getId() { + return folderId; + } + @Override public PCloudFolder getParent() { return parent; @@ -44,6 +44,6 @@ class PCloudFolder implements CloudFolder, PCloudNode { @Override public PCloudFolder withCloud(Cloud cloud) { - return new PCloudFolder(parent.withCloud(cloud), folderid, name, path); + return new PCloudFolder(parent.withCloud(cloud), name, path, folderId); } } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index c1dd58c5..a4ed013a 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -52,7 +52,7 @@ class PCloudImpl { private final RootPCloudFolder root; private final Context context; - PCloudImpl(PCloudCloud cloud, Context context, PCloudIdCache idCache) { + PCloudImpl(Context context, PCloudCloud cloud, PCloudIdCache idCache) { if (cloud.accessToken() == null) { throw new NoAuthenticationProvidedException(cloud); } @@ -129,7 +129,7 @@ class PCloudImpl { return idCache.cache(PCloudCloudNodeFactory.file(parent, file.get().asFile())); } - return PCloudCloudNodeFactory.file(parent, name, size, parent.getPath() + '/' + name); + return PCloudCloudNodeFactory.file(parent, name, size); } public PCloudFolder folder(PCloudFolder parent, String name) { @@ -147,7 +147,7 @@ class PCloudImpl { if (folder.isPresent()) { return idCache.cache(PCloudCloudNodeFactory.folder(parent, folder.get().asFolder())); } - return PCloudCloudNodeFactory.folder(parent, name, parent.getPath() + '/' + name); + return PCloudCloudNodeFactory.folder(parent, name); } public boolean exists(PCloudNode node) throws ApiError, IOException { @@ -195,9 +195,9 @@ class PCloudImpl { if (folder.getParent().getId() == null) { folder = new PCloudFolder( // create(folder.getParent()), // - folder.getId(), // - folder.getName(), // - folder.getPath()); + folder.getName(), folder.getPath(), folder.getId() // + // + ); } RemoteFolder createdFolder = client() // diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java index 60fc42e2..90fde0aa 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java @@ -9,7 +9,7 @@ class RootPCloudFolder extends PCloudFolder { private static final long rootFolderId = 0L; public RootPCloudFolder(PCloudCloud cloud) { - super(null, rootFolderId, "", ""); + super(null, "", "", rootFolderId); this.cloud = cloud; } From 091f7eeacf403a41145340ed3e2b29ce2662f539 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 07:39:06 +0100 Subject: [PATCH 50/93] fix: read pCloud Client ID from BuildConfig --- .../presentation/presenter/CloudConnectionListPresenter.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index 96799a22..fbf9b4c6 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -23,6 +23,7 @@ import org.cryptomator.domain.usecases.cloud.RemoveCloudUseCase import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase import org.cryptomator.domain.usecases.vault.GetVaultListUseCase import org.cryptomator.generator.Callback +import org.cryptomator.presentation.BuildConfig import org.cryptomator.presentation.R import org.cryptomator.presentation.exception.ExceptionHandlers import org.cryptomator.presentation.intent.Intents @@ -136,7 +137,7 @@ class CloudConnectionListPresenter @Inject constructor( // this.context(), AuthorizationRequest.create() .setType(AuthorizationRequest.Type.TOKEN) - .setClientId("tsAamgqqwk7") + .setClientId(BuildConfig.PCLOUD_CLIENT_ID) .setForceAccessApproval(true) .addPermission("manageshares") .build()) From b6cebf16fb29e3533aeb1833274806a1bcb4a549 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 15:33:31 +0100 Subject: [PATCH 51/93] fix: move decrypt() for accessToken --- .../data/cloud/pcloud/PCloudClientFactory.java | 9 ++++++++- .../org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 8 +------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java index 8c943367..019d8552 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java @@ -8,6 +8,7 @@ import com.pcloud.sdk.PCloudSdk; import org.cryptomator.data.cloud.okhttplogging.HttpLoggingInterceptor; import org.cryptomator.util.SharedPreferencesHandler; +import org.cryptomator.util.crypto.CredentialCryptor; import org.cryptomator.util.file.LruFileCacheUtil; import okhttp3.Cache; @@ -49,6 +50,12 @@ class PCloudClientFactory { OkHttpClient okHttpClient = okHttpClientBuilder.build(); - return PCloudSdk.newClientBuilder().authenticator(Authenticators.newOAuthAuthenticator(accessToken)).withClient(okHttpClient).apiHost(url).create(); + return PCloudSdk.newClientBuilder().authenticator(Authenticators.newOAuthAuthenticator(decrypt(accessToken, context))).withClient(okHttpClient).apiHost(url).create(); + } + + private String decrypt(String password, Context context) { + return CredentialCryptor // + .getInstance(context) // + .decrypt(password); } } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index a4ed013a..771da193 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -64,13 +64,7 @@ class PCloudImpl { } private ApiClient client() { - return clientFactory.getClient(decrypt(cloud.accessToken()), cloud.url(), context); - } - - private String decrypt(String password) { - return CredentialCryptor // - .getInstance(context) // - .decrypt(password); + return clientFactory.getClient(cloud.accessToken(), cloud.url(), context); } public PCloudFolder root() { From 90c7c144715f35abcbdf7a175f4bf75258b604c2 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 15:40:54 +0100 Subject: [PATCH 52/93] fix: simplifications - Change all `CloudNode` to `PCloudNode` - Check `replace` before `exists()` - Move definition of relocationEntry below exception - Change `CloudFile` to `PCloudFile` --- .../pcloud/PCloudCloudContentRepository.java | 2 +- .../cryptomator/data/cloud/pcloud/PCloudImpl.java | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java index d83a4bef..dfeb7c27 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java @@ -96,7 +96,7 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository list(PCloudFolder folder) throws BackendException { + public List list(PCloudFolder folder) throws BackendException { try { return cloud.list(folder); } catch (ApiError | IOException e) { diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 771da193..25e9297d 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -165,8 +165,8 @@ class PCloudImpl { } } - public List list(PCloudFolder folder) throws ApiError, IOException { - List result = new ArrayList<>(); + public List list(PCloudFolder folder) throws ApiError, IOException { + List result = new ArrayList<>(); Long folderId = folder.getId(); RemoteFolder listFolderResult; @@ -201,12 +201,13 @@ class PCloudImpl { PCloudCloudNodeFactory.folder(folder.getParent(), createdFolder)); } - public CloudNode move(PCloudNode source, PCloudNode target) throws ApiError, BackendException, IOException { - RemoteEntry relocationResult; + public PCloudNode move(PCloudNode source, PCloudNode target) throws ApiError, BackendException, IOException { if (exists(target)) { throw new CloudNodeAlreadyExistsException(target.getName()); } + RemoteEntry relocationResult; + if (source instanceof PCloudFolder) { relocationResult = client().moveFolder(source.getId(), target.getParent().getId()).execute(); if (!relocationResult.name().equals(target.getName())) { @@ -225,7 +226,7 @@ class PCloudImpl { public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) throws ApiError, BackendException, IOException { - if (exists(file) && !replace) { + if (!replace && exists(file)) { throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false"); } @@ -278,10 +279,10 @@ class PCloudImpl { .execute(); } - public void read(CloudFile file, OutputStream data, final ProgressAware progressAware) throws ApiError, IOException { + public void read(PCloudFile file, OutputStream data, final ProgressAware progressAware) throws ApiError, IOException { progressAware.onProgress(Progress.started(DownloadState.download(file))); - Long fileId = ((PCloudFile)file).getId(); + Long fileId = file.getId(); if (fileId == null) { fileId = idCache.get(file.getPath()).getId(); } From 0bb01fec0ed712274dcc7c2038b77f8bf41ff61f Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 15:47:28 +0100 Subject: [PATCH 53/93] fix: remove unused imports --- .../data/cloud/pcloud/PCloudCloudContentRepository.java | 1 - .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 3 --- 2 files changed, 4 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java index dfeb7c27..79620507 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java @@ -5,7 +5,6 @@ import android.content.Context; import com.pcloud.sdk.ApiError; import org.cryptomator.data.cloud.InterceptingCloudContentRepository; -import org.cryptomator.domain.CloudNode; import org.cryptomator.domain.PCloudCloud; import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 25e9297d..aaca516d 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -15,8 +15,6 @@ import com.pcloud.sdk.UploadOptions; import com.pcloud.sdk.UserInfo; import org.cryptomator.data.util.CopyStream; -import org.cryptomator.domain.CloudFile; -import org.cryptomator.domain.CloudNode; import org.cryptomator.domain.PCloudCloud; import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; @@ -28,7 +26,6 @@ import org.cryptomator.domain.usecases.cloud.DownloadState; import org.cryptomator.domain.usecases.cloud.Progress; import org.cryptomator.domain.usecases.cloud.UploadState; import org.cryptomator.util.Optional; -import org.cryptomator.util.crypto.CredentialCryptor; import java.io.IOException; import java.io.OutputStream; From 73b3dc1459711def387f8b2030d0886bd3ba3f46 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 15:47:57 +0100 Subject: [PATCH 54/93] fix: remove unused pCloud related logout code --- .../cryptomator/domain/usecases/cloud/LogoutCloud.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/LogoutCloud.java b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/LogoutCloud.java index 661acd6a..3ea926a9 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/LogoutCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/LogoutCloud.java @@ -4,7 +4,6 @@ import org.cryptomator.domain.Cloud; import org.cryptomator.domain.DropboxCloud; import org.cryptomator.domain.GoogleDriveCloud; import org.cryptomator.domain.OnedriveCloud; -import org.cryptomator.domain.PCloudCloud; import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.repository.CloudContentRepository; import org.cryptomator.domain.repository.CloudRepository; @@ -48,14 +47,6 @@ class LogoutCloud { .withUsername(null) // .withAccessToken(null) // .build(); - } else if (cloud instanceof PCloudCloud) { - //TODO proper logout? - return PCloudCloud // - .aCopyOf((PCloudCloud) cloud) // - .withUsername(null) // - .withAccessToken(null) // - .withUrl(null) // - .build(); } throw new IllegalStateException("Logout not supported for cloud with type " + cloud.type()); } From 9fc4626e614f39e2f0d11064a9a207a4d7ba530c Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 15:52:13 +0100 Subject: [PATCH 55/93] fix: remove PCLOUD_CLIENT_ID from manifestPlaceholders --- presentation/build.gradle | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/presentation/build.gradle b/presentation/build.gradle index b43b1b19..8a1c5f99 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -50,11 +50,8 @@ android { useProguard false buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY') + "\"" + manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY')] buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID') + "\"" - manifestPlaceholders = [ - DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY'), - PCLOUD_CLIENT_ID: getApiKey('PCLOUD_CLIENT_ID') - ] resValue "string", "app_id", androidApplicationId } @@ -68,11 +65,8 @@ android { testCoverageEnabled false buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY_DEBUG') + "\"" + manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY_DEBUG')] buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID_DEBUG') + "\"" - manifestPlaceholders = [ - DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY_DEBUG'), - PCLOUD_CLIENT_ID: getApiKey('PCLOUD_CLIENT_ID_DEBUG') - ] applicationIdSuffix ".debug" versionNameSuffix '-DEBUG' From f17df0e17922408e564d7c4ff3d88a9b9180c8cd Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 15:53:38 +0100 Subject: [PATCH 56/93] fix: don't specify activityResult as optional --- .../presentation/presenter/CloudConnectionListPresenter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index fbf9b4c6..f6618d1f 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -185,8 +185,8 @@ class CloudConnectionListPresenter @Inject constructor( // } @Callback - fun pCloudAuthenticationFinished(activityResult: ActivityResult?) { - val authData: AuthorizationData = AuthorizationActivity.getResult(activityResult!!.intent()) + fun pCloudAuthenticationFinished(activityResult: ActivityResult) { + val authData: AuthorizationData = AuthorizationActivity.getResult(activityResult.intent()) val result: AuthorizationResult = authData.result when (result) { From d92e4b0daf54295719a441d1bb2a47a5b60f5906 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 16:16:42 +0100 Subject: [PATCH 57/93] fix: rename PCloudCloud stuff to PCloud --- ...tory.java => PCloudContentRepository.java} | 20 +++++----- ...va => PCloudContentRepositoryFactory.java} | 8 ++-- .../data/cloud/pcloud/PCloudImpl.java | 38 +++++++++---------- ...odeFactory.java => PCloudNodeFactory.java} | 5 +-- .../data/cloud/pcloud/RootPCloudFolder.java | 10 ++--- .../data/db/mappers/CloudEntityMapper.java | 12 +++--- .../CloudContentRepositoryFactories.java | 4 +- .../domain/{PCloudCloud.java => PCloud.java} | 24 ++++++------ .../usecases/cloud/ConnectToPCloud.java | 6 +-- .../{PCloudCloudModel.kt => PCloudModel.kt} | 8 ++-- .../model/mappers/CloudModelMapper.kt | 4 +- .../presenter/CloudConnectionListPresenter.kt | 16 ++++---- .../presenter/CloudSettingsPresenter.kt | 12 +++--- .../ui/adapter/CloudConnectionListAdapter.kt | 8 ++-- .../CloudConnectionSettingsBottomSheet.kt | 6 +-- .../presenter/AuthenticateCloudPresenter.kt | 6 +-- 16 files changed, 92 insertions(+), 95 deletions(-) rename data/src/main/java/org/cryptomator/data/cloud/pcloud/{PCloudCloudContentRepository.java => PCloudContentRepository.java} (90%) rename data/src/main/java/org/cryptomator/data/cloud/pcloud/{PCloudCloudContentRepositoryFactory.java => PCloudContentRepositoryFactory.java} (69%) rename data/src/main/java/org/cryptomator/data/cloud/pcloud/{PCloudCloudNodeFactory.java => PCloudNodeFactory.java} (94%) rename domain/src/main/java/org/cryptomator/domain/{PCloudCloud.java => PCloud.java} (78%) rename presentation/src/main/java/org/cryptomator/presentation/model/{PCloudCloudModel.kt => PCloudModel.kt} (71%) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java similarity index 90% rename from data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java rename to data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java index 79620507..a26e39fd 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java @@ -5,7 +5,7 @@ import android.content.Context; import com.pcloud.sdk.ApiError; import org.cryptomator.data.cloud.InterceptingCloudContentRepository; -import org.cryptomator.domain.PCloudCloud; +import org.cryptomator.domain.PCloud; import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; import org.cryptomator.domain.exception.FatalBackendException; @@ -26,11 +26,11 @@ import java.util.List; import static org.cryptomator.util.ExceptionUtil.contains; -class PCloudCloudContentRepository extends InterceptingCloudContentRepository { +class PCloudContentRepository extends InterceptingCloudContentRepository { - private final PCloudCloud cloud; + private final PCloud cloud; - public PCloudCloudContentRepository(PCloudCloud cloud, Context context, PCloudIdCache idCache) { + public PCloudContentRepository(PCloud cloud, Context context, PCloudIdCache idCache) { super(new Intercepted(cloud, context, idCache)); this.cloud = cloud; } @@ -53,20 +53,20 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository { + private static class Intercepted implements CloudContentRepository { private final PCloudImpl cloud; - public Intercepted(PCloudCloud cloud, Context context, PCloudIdCache idCache) { + public Intercepted(PCloud cloud, Context context, PCloudIdCache idCache) { this.cloud = new PCloudImpl(context, cloud, idCache); } - public PCloudFolder root(PCloudCloud cloud) { + public PCloudFolder root(PCloud cloud) { return this.cloud.root(); } @Override - public PCloudFolder resolve(PCloudCloud cloud, String path) { + public PCloudFolder resolve(PCloud cloud, String path) { return this.cloud.resolve(path); } @@ -194,7 +194,7 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository size) { if (parent.getId() == null) { - return PCloudCloudNodeFactory.file(parent, name, size); + return PCloudNodeFactory.file(parent, name, size); } - String path = PCloudCloudNodeFactory.getNodePath(parent, name); + String path = PCloudNodeFactory.getNodePath(parent, name); PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); if (nodeInfo != null && !nodeInfo.isFolder()) { - return PCloudCloudNodeFactory.file(parent, name, size, path, nodeInfo.getId()); + return PCloudNodeFactory.file(parent, name, size, path, nodeInfo.getId()); } Optional file = findEntry(parent.getId(), name, false); if (file.isPresent()) { - return idCache.cache(PCloudCloudNodeFactory.file(parent, file.get().asFile())); + return idCache.cache(PCloudNodeFactory.file(parent, file.get().asFile())); } - return PCloudCloudNodeFactory.file(parent, name, size); + return PCloudNodeFactory.file(parent, name, size); } public PCloudFolder folder(PCloudFolder parent, String name) { if (parent.getId() == null) { - return PCloudCloudNodeFactory.folder(parent, name); + return PCloudNodeFactory.folder(parent, name); } - String path = PCloudCloudNodeFactory.getNodePath(parent, name); + String path = PCloudNodeFactory.getNodePath(parent, name); PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); if (nodeInfo != null && nodeInfo.isFolder()) { - return PCloudCloudNodeFactory.folder(parent, name, path, nodeInfo.getId()); + return PCloudNodeFactory.folder(parent, name, path, nodeInfo.getId()); } Optional folder = findEntry(parent.getId(), name, true); if (folder.isPresent()) { - return idCache.cache(PCloudCloudNodeFactory.folder(parent, folder.get().asFolder())); + return idCache.cache(PCloudNodeFactory.folder(parent, folder.get().asFolder())); } - return PCloudCloudNodeFactory.folder(parent, name); + return PCloudNodeFactory.folder(parent, name); } public boolean exists(PCloudNode node) throws ApiError, IOException { try { if (node instanceof PCloudFolder) { RemoteFolder remoteFolder = client().listFolder(node.getPath()).execute(); - idCache.add(PCloudCloudNodeFactory.folder(node.getParent(), remoteFolder)); + idCache.add(PCloudNodeFactory.folder(node.getParent(), remoteFolder)); } else { RemoteFile remoteFile = client().stat(node.getPath()).execute(); - idCache.add(PCloudCloudNodeFactory.file(node.getParent(), remoteFile)); + idCache.add(PCloudNodeFactory.file(node.getParent(), remoteFile)); } return true; } catch (ApiError e) { @@ -177,7 +177,7 @@ class PCloudImpl { List entryMetadata = listFolderResult.children(); for (RemoteEntry metadata : entryMetadata) { - result.add(idCache.cache(PCloudCloudNodeFactory.from(folder, metadata))); + result.add(idCache.cache(PCloudNodeFactory.from(folder, metadata))); } return result; } @@ -195,7 +195,7 @@ class PCloudImpl { .createFolder(folder.getParent().getId(), folder.getName()) // .execute(); return idCache.cache( // - PCloudCloudNodeFactory.folder(folder.getParent(), createdFolder)); + PCloudNodeFactory.folder(folder.getParent(), createdFolder)); } public PCloudNode move(PCloudNode source, PCloudNode target) throws ApiError, BackendException, IOException { @@ -218,7 +218,7 @@ class PCloudImpl { } idCache.remove(source); - return idCache.cache(PCloudCloudNodeFactory.from(target.getParent(), relocationResult)); + return idCache.cache(PCloudNodeFactory.from(target.getParent(), relocationResult)); } public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) @@ -241,7 +241,7 @@ class PCloudImpl { progressAware.onProgress(Progress.completed(UploadState.upload(file))); - return idCache.cache(PCloudCloudNodeFactory.file(file.getParent(), uploadedFile)); + return idCache.cache(PCloudNodeFactory.file(file.getParent(), uploadedFile)); } private RemoteFile uploadFile(final PCloudFile file, DataSource data, final ProgressAware progressAware, UploadOptions uploadOptions, final long size) // diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudNodeFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java similarity index 94% rename from data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudNodeFactory.java rename to data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java index 9589cbaa..039add0a 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudNodeFactory.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java @@ -1,15 +1,12 @@ package org.cryptomator.data.cloud.pcloud; -import com.google.api.services.drive.model.File; import com.pcloud.sdk.RemoteEntry; import com.pcloud.sdk.RemoteFile; import com.pcloud.sdk.RemoteFolder; import org.cryptomator.util.Optional; -import java.util.Date; - -class PCloudCloudNodeFactory { +class PCloudNodeFactory { public static PCloudFile file(PCloudFolder parent, RemoteFile file) { return new PCloudFile(parent, file.name(), getNodePath(parent, file.name()), file.fileId(), Optional.ofNullable(file.size()), Optional.ofNullable(file.lastModified())); diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java index 90fde0aa..28b45480 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java @@ -1,25 +1,25 @@ package org.cryptomator.data.cloud.pcloud; import org.cryptomator.domain.Cloud; -import org.cryptomator.domain.PCloudCloud; +import org.cryptomator.domain.PCloud; class RootPCloudFolder extends PCloudFolder { - private final PCloudCloud cloud; + private final PCloud cloud; private static final long rootFolderId = 0L; - public RootPCloudFolder(PCloudCloud cloud) { + public RootPCloudFolder(PCloud cloud) { super(null, "", "", rootFolderId); this.cloud = cloud; } @Override - public PCloudCloud getCloud() { + public PCloud getCloud() { return cloud; } @Override public PCloudFolder withCloud(Cloud cloud) { - return new RootPCloudFolder((PCloudCloud) cloud); + return new RootPCloudFolder((PCloud) cloud); } } diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java index da4f3fbf..20be3e7a 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java @@ -4,7 +4,7 @@ import org.cryptomator.data.db.entities.CloudEntity; import org.cryptomator.domain.Cloud; import org.cryptomator.domain.CloudType; import org.cryptomator.domain.DropboxCloud; -import org.cryptomator.domain.PCloudCloud; +import org.cryptomator.domain.PCloud; import org.cryptomator.domain.GoogleDriveCloud; import org.cryptomator.domain.LocalStorageCloud; import org.cryptomator.domain.OnedriveCloud; @@ -17,7 +17,7 @@ import static org.cryptomator.domain.DropboxCloud.aDropboxCloud; import static org.cryptomator.domain.GoogleDriveCloud.aGoogleDriveCloud; import static org.cryptomator.domain.LocalStorageCloud.aLocalStorage; import static org.cryptomator.domain.OnedriveCloud.aOnedriveCloud; -import static org.cryptomator.domain.PCloudCloud.aPCloudCloud; +import static org.cryptomator.domain.PCloud.aPCloud; import static org.cryptomator.domain.WebDavCloud.aWebDavCloudCloud; @Singleton @@ -50,7 +50,7 @@ public class CloudEntityMapper extends EntityMapper { .withUsername(entity.getUsername()) // .build(); case PCLOUD: - return aPCloudCloud() // + return aPCloud() // .withId(entity.getId()) // .withUrl(entity.getWebdavUrl()) // .withAccessToken(entity.getAccessToken()) // @@ -92,9 +92,9 @@ public class CloudEntityMapper extends EntityMapper { result.setUsername(((OnedriveCloud) domainObject).username()); break; case PCLOUD: - result.setAccessToken(((PCloudCloud) domainObject).accessToken()); - result.setWebdavUrl(((PCloudCloud) domainObject).url()); - result.setUsername(((PCloudCloud) domainObject).username()); + result.setAccessToken(((PCloud) domainObject).accessToken()); + result.setWebdavUrl(((PCloud) domainObject).url()); + result.setUsername(((PCloud) domainObject).username()); break; case LOCAL: result.setAccessToken(((LocalStorageCloud) domainObject).rootUri()); diff --git a/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java b/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java index eea89b15..ce4f9b41 100644 --- a/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java +++ b/data/src/notFoss/java/org/cryptomator/data/cloud/CloudContentRepositoryFactories.java @@ -5,7 +5,7 @@ import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory; import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory; import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory; import org.cryptomator.data.cloud.onedrive.OnedriveCloudContentRepositoryFactory; -import org.cryptomator.data.cloud.pcloud.PCloudCloudContentRepositoryFactory; +import org.cryptomator.data.cloud.pcloud.PCloudContentRepositoryFactory; import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory; import org.cryptomator.data.repository.CloudContentRepositoryFactory; import org.jetbrains.annotations.NotNull; @@ -26,7 +26,7 @@ public class CloudContentRepositoryFactories implements Iterable() CloudTypeModel.DROPBOX -> DropboxCloudModel(domainObject) CloudTypeModel.GOOGLE_DRIVE -> GoogleDriveCloudModel(domainObject) CloudTypeModel.ONEDRIVE -> OnedriveCloudModel(domainObject) - CloudTypeModel.PCLOUD -> PCloudCloudModel(domainObject) + CloudTypeModel.PCLOUD -> PCloudModel(domainObject) CloudTypeModel.CRYPTO -> CryptoCloudModel(domainObject) CloudTypeModel.LOCAL -> LocalStorageModel(domainObject) CloudTypeModel.WEBDAV -> WebDavCloudModel(domainObject) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index f6618d1f..0cb7a417 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -13,7 +13,7 @@ import com.pcloud.sdk.AuthorizationRequest import com.pcloud.sdk.AuthorizationResult import org.cryptomator.domain.Cloud import org.cryptomator.domain.LocalStorageCloud -import org.cryptomator.domain.PCloudCloud +import org.cryptomator.domain.PCloud import org.cryptomator.domain.Vault import org.cryptomator.domain.di.PerView import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase @@ -194,7 +194,7 @@ class CloudConnectionListPresenter @Inject constructor( // val accessToken: String = CredentialCryptor // .getInstance(this.context()) // .encrypt(authData.token) - val pCloudSkeleton: PCloudCloud = PCloudCloud.aPCloudCloud() // + val pCloudSkeleton: PCloud = PCloud.aPCloud() // .withAccessToken(accessToken) .withUrl(authData.apiHost) .build(); @@ -202,7 +202,7 @@ class CloudConnectionListPresenter @Inject constructor( // .withCloud(pCloudSkeleton) // .run(object : DefaultResultHandler() { override fun onSuccess(username: String?) { - prepareForSavingPCloudCloud(PCloudCloud.aCopyOf(pCloudSkeleton).withUsername(username).build()) + prepareForSavingPCloud(PCloud.aCopyOf(pCloudSkeleton).withUsername(username).build()) } }) } @@ -219,15 +219,15 @@ class CloudConnectionListPresenter @Inject constructor( // } } - fun prepareForSavingPCloudCloud(cloud: PCloudCloud) { + fun prepareForSavingPCloud(cloud: PCloud) { getCloudsUseCase // .withCloudType(CloudTypeModel.valueOf(selectedCloudType.get())) // .run(object : DefaultResultHandler>() { override fun onSuccess(clouds: List) { clouds.firstOrNull { - (it as PCloudCloud).username() == cloud.username() - }?.let { it as PCloudCloud - saveCloud(PCloudCloud.aCopyOf(it) // + (it as PCloud).username() == cloud.username() + }?.let { it as PCloud + saveCloud(PCloud.aCopyOf(it) // .withUrl(cloud.url()) .withAccessToken(it.accessToken()) .build()) @@ -236,7 +236,7 @@ class CloudConnectionListPresenter @Inject constructor( // }) } - fun saveCloud(cloud: PCloudCloud) { + fun saveCloud(cloud: PCloud) { addOrChangeCloudConnectionUseCase // .withCloud(cloud) // .run(object : DefaultResultHandler() { diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt index 1d3411eb..4c00f108 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudSettingsPresenter.kt @@ -2,7 +2,7 @@ package org.cryptomator.presentation.presenter import org.cryptomator.domain.Cloud import org.cryptomator.domain.LocalStorageCloud -import org.cryptomator.domain.PCloudCloud +import org.cryptomator.domain.PCloud import org.cryptomator.domain.WebDavCloud import org.cryptomator.domain.di.PerView import org.cryptomator.domain.exception.FatalBackendException @@ -17,7 +17,7 @@ import org.cryptomator.presentation.intent.Intents import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel import org.cryptomator.presentation.model.LocalStorageModel -import org.cryptomator.presentation.model.PCloudCloudModel +import org.cryptomator.presentation.model.PCloudModel import org.cryptomator.presentation.model.WebDavCloudModel import org.cryptomator.presentation.model.mappers.CloudModelMapper import org.cryptomator.presentation.ui.activity.view.CloudSettingsView @@ -62,7 +62,7 @@ class CloudSettingsPresenter @Inject constructor( // } private fun isWebdavOrPCloudOrLocal(cloudModel: CloudModel): Boolean { - return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel || cloudModel is PCloudCloudModel + return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel || cloudModel is PCloudModel } private fun loginCloud(cloudModel: CloudModel) { @@ -127,7 +127,7 @@ class CloudSettingsPresenter @Inject constructor( // .toMutableList() // .also { it.add(aWebdavCloud()) - it.add(aPCloudCloud()) + it.add(aPCloud()) it.add(aLocalCloud()) } view?.render(cloudModel) @@ -137,8 +137,8 @@ class CloudSettingsPresenter @Inject constructor( // return WebDavCloudModel(WebDavCloud.aWebDavCloudCloud().build()) } - private fun aPCloudCloud(): PCloudCloudModel { - return PCloudCloudModel(PCloudCloud.aPCloudCloud().build()) + private fun aPCloud(): PCloudModel { + return PCloudModel(PCloud.aPCloud().build()) } private fun aLocalCloud(): CloudModel { diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt index fbba49ce..40793409 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt @@ -7,7 +7,7 @@ import org.cryptomator.domain.exception.FatalBackendException import org.cryptomator.presentation.R import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.LocalStorageModel -import org.cryptomator.presentation.model.PCloudCloudModel +import org.cryptomator.presentation.model.PCloudModel import org.cryptomator.presentation.model.WebDavCloudModel import org.cryptomator.presentation.model.comparator.CloudModelComparator import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter.CloudConnectionHolder @@ -55,8 +55,8 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter bindViewForWebDAV(cloudModel as WebDavCloudModel) - CloudTypeModel.PCLOUD -> bindViewForPCloud(cloudModel as PCloudCloudModel) + CloudTypeModel.PCLOUD -> bindViewForPCloud(cloudModel as PCloudModel) CloudTypeModel.LOCAL -> bindViewForLocal(cloudModel as LocalStorageModel) else -> throw IllegalStateException("Cloud model is not binded in the view") } @@ -61,7 +61,7 @@ class CloudConnectionSettingsBottomSheet : BaseBottomSheet { From c8a2c1dc19949dd94e85e6aa7c61d12f6fad24e2 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 16:18:58 +0100 Subject: [PATCH 58/93] feat: implement toasts for authentication errors --- .../presenter/CloudConnectionListPresenter.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index 0cb7a417..8013af11 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -206,15 +206,17 @@ class CloudConnectionListPresenter @Inject constructor( // } }) } - AuthorizationResult.ACCESS_DENIED -> //TODO: Add proper handling for denied grants. - Log.d("pCloud", "Account access denied") + AuthorizationResult.ACCESS_DENIED -> { + Timber.tag("CloudConnListPresenter").e("Account access denied") + view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud))) + } AuthorizationResult.AUTH_ERROR -> { - //TODO: Add error handling. - Log.d("pCloud", """Account access grant error: ${authData.errorMessage}""".trimIndent()) + Timber.tag("CloudConnListPresenter").e("""Account access grant error: ${authData.errorMessage}""".trimIndent()) + view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud))) } AuthorizationResult.CANCELLED -> { - //TODO: Handle cancellation. - Log.d("pCloud", "Account access grant cancelled:") + Timber.tag("CloudConnListPresenter").i("Account access grant cancelled") + view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud))) } } } From f7411ea286cb0178e467959f00af9d3db4cac2c1 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 17:12:14 +0100 Subject: [PATCH 59/93] fix: remove unused import --- .../main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java index af04d938..29f22567 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java @@ -1,7 +1,5 @@ package org.cryptomator.data.cloud.pcloud; -import org.cryptomator.domain.CloudNode; - interface PCloudNode extends PCloudIdCloudNode { @Override From c1ca71be82ff46d2fc77ce6a3549a2565086ec74 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 17 Mar 2021 17:37:02 +0100 Subject: [PATCH 60/93] fix: remove `PCloudIdCloudNode` interface --- .../org/cryptomator/data/cloud/pcloud/PCloudIdCache.java | 8 ++++---- .../cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java | 9 --------- .../org/cryptomator/data/cloud/pcloud/PCloudNode.java | 5 +++-- 3 files changed, 7 insertions(+), 15 deletions(-) delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java index 7c22b0cd..5862fedd 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java @@ -19,12 +19,12 @@ class PCloudIdCache { return cache.get(path); } - T cache(T value) { + T cache(T value) { add(value); return value; } - public void add(PCloudIdCloudNode node) { + public void add(PCloudNode node) { add(node.getPath(), new NodeInfo(node)); } @@ -32,7 +32,7 @@ class PCloudIdCache { cache.put(path, info); } - public void remove(PCloudIdCloudNode node) { + public void remove(PCloudNode node) { remove(node.getPath()); } @@ -55,7 +55,7 @@ class PCloudIdCache { private final Long id; private final boolean isFolder; - private NodeInfo(PCloudIdCloudNode node) { + private NodeInfo(PCloudNode node) { this(node.getId(), node instanceof CloudFolder); } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java deleted file mode 100644 index 3490cb53..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.cryptomator.data.cloud.pcloud; - -import org.cryptomator.domain.CloudNode; - -interface PCloudIdCloudNode extends CloudNode { - - Long getId(); - -} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java index 29f22567..e58b6775 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java @@ -1,8 +1,9 @@ package org.cryptomator.data.cloud.pcloud; -interface PCloudNode extends PCloudIdCloudNode { +import org.cryptomator.domain.CloudNode; + +interface PCloudNode extends CloudNode { - @Override Long getId(); @Override From 369e1d1945fe26926f4eef840d43a258527ccece Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 18 Mar 2021 17:09:56 +0100 Subject: [PATCH 61/93] feat: add error code for revoked access token --- .../org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java | 1 + 1 file changed, 1 insertion(+) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java index 9985ea0b..710029a9 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java @@ -24,6 +24,7 @@ public enum PCloudApiErrorCodes { CANNOT_MOVE_FOLDER_INTO_SUBFOLDER_OF_ITSELF(2043), FILE_OR_FOLDER_NOT_FOUND(2055), INVALID_ACCESS_TOKEN(2094), + ACCESS_TOKEN_REVOKED(2095), TOO_MANY_LOGIN_TRIES_FROM_IP(4000), INTERNAL_ERROR(5000), INTERNAL_UPLOAD_ERROR(5001); From 80a0d7cdcd960d00525c861b19d9ad90b0f51e2e Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 18 Mar 2021 17:20:09 +0100 Subject: [PATCH 62/93] fix: throw WrongCredentialsException in findEntry() --- .../data/cloud/pcloud/PCloudContentRepository.java | 7 +++++-- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java index a26e39fd..88dd65f0 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java @@ -48,8 +48,11 @@ class PCloudContentRepository extends InterceptingCloudContentRepository Date: Thu, 18 Mar 2021 17:20:53 +0100 Subject: [PATCH 63/93] fix: get root folder id from RemoteFolder --- .../org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java index 28b45480..058cad0d 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java @@ -1,12 +1,14 @@ package org.cryptomator.data.cloud.pcloud; +import com.pcloud.sdk.RemoteFolder; + import org.cryptomator.domain.Cloud; import org.cryptomator.domain.PCloud; class RootPCloudFolder extends PCloudFolder { private final PCloud cloud; - private static final long rootFolderId = 0L; + private static final long rootFolderId = RemoteFolder.ROOT_FOLDER_ID; public RootPCloudFolder(PCloud cloud) { super(null, "", "", rootFolderId); From d9d88df49a569e3ae6ed75e2364b8666030987ae Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 18 Mar 2021 20:47:56 +0100 Subject: [PATCH 64/93] fix: use ROOT_FOLDER_ID inline --- .../org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java index 058cad0d..60ac96f0 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java @@ -8,10 +8,9 @@ import org.cryptomator.domain.PCloud; class RootPCloudFolder extends PCloudFolder { private final PCloud cloud; - private static final long rootFolderId = RemoteFolder.ROOT_FOLDER_ID; public RootPCloudFolder(PCloud cloud) { - super(null, "", "", rootFolderId); + super(null, "", "", (long) RemoteFolder.ROOT_FOLDER_ID); this.cloud = cloud; } From 1146a5e8fcd2fa774cb87c9c79b7fccce497504c Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 18 Mar 2021 21:41:58 +0100 Subject: [PATCH 65/93] fix: file upload with unencoded filename There seems to be an issue with unencoded filenames when uploading a new file to the vault. E.g. file name "YYbfUTn6ViuRkXzeXXYAAu8z6DwFMrTNtPY3zpjn24RIUmzqqi4=.c9r" will result in a broken upload. Encoding the filename will solve this: "YYbfUTn6ViuRkXzeXXYAAu8z6DwFMrTNtPY3zpjn24RIUmzqqi4%3D.c9r" However, this will actually result in the file's name being encoded. If the filename and the encoded filename do not match we need to rename the uploaded file using the correct filename. --- .../cryptomator/data/cloud/pcloud/PCloudImpl.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index a0fe02ff..899c8913 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -30,6 +30,7 @@ import org.cryptomator.util.Optional; import java.io.IOException; import java.io.OutputStream; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -278,9 +279,17 @@ class PCloudImpl { parentFolderId = idCache.get(file.getParent().getPath()).getId(); } - return client() // - .createFile(parentFolderId, file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // - .execute(); + String filename = file.getName(); + String encodedFilename = URLEncoder.encode(filename, "UTF-8"); + + RemoteFile newFile = client() // + .createFile(parentFolderId, encodedFilename, pCloudDataSource, new Date(), listener, uploadOptions) // + .execute(); + if (!filename.equals(encodedFilename)) { + return client().renameFile(newFile.fileId(), filename).execute(); + } + + return newFile; } public void read(PCloudFile file, OutputStream data, final ProgressAware progressAware) throws ApiError, IOException { From 2fcf53b633fc8ca2a8f2e2a37e0bd4ca51338266 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 19 Mar 2021 13:54:48 +0100 Subject: [PATCH 66/93] feat: add exception handling --- .../data/cloud/pcloud/PCloudApiError.java | 90 +++++++ .../cloud/pcloud/PCloudApiErrorCodes.java | 39 --- .../cloud/pcloud/PCloudContentRepository.java | 87 +++--- .../data/cloud/pcloud/PCloudImpl.java | 252 +++++++++++------- 4 files changed, 283 insertions(+), 185 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java new file mode 100644 index 00000000..5c594ae6 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java @@ -0,0 +1,90 @@ +package org.cryptomator.data.cloud.pcloud; + +public class PCloudApiError { + + public enum PCloudApiErrorCodes { + LOGIN_REQUIRED(1000), + NO_FULL_PATH_OR_NAME_FOLDER_ID_PROVIDED(1001), + NO_FULL_PATH_OR_FOLDER_ID_PROVIDED(1002), + NO_FILE_ID_OR_PATH_PROVIDED(1004), + INVALID_DATE_TIME_FORMAT(1013), + NO_DESTINATION_PROVIDED(1016), + INVALID_FOLDER_ID(1017), + INVALID_DESTINATION(1037), + PROVIDE_URL(1040), + UPLOAD_NOT_FOUND(1900), + TRANSFER_NOT_FOUND(1902), + LOGIN_FAILED(2000), + INVALID_FILE_OR_FOLDER_NAME(2001), + COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST(2002), + ACCESS_DENIED(2003), + FILE_OR_FOLDER_ALREADY_EXISTS(2004), + DIRECTORY_DOES_NOT_EXIST(2005), + FOLDER_NOT_EMPTY(2006), + CANNOT_DELETE_ROOT_FOLDER(2007), + USER_OVER_QUOTA(2008), + FILE_NOT_FOUND(2009), + INVALID_PATH(2010), + SHARED_FOLDER_IN_SHARED_FOLDER(2023), + ACTIVE_SHARES_OR_SHAREREQUESTS_PRESENT(2028), + CONNECTION_BROKE(2041), + CANNOT_RENAME_ROOT_FOLDER(2042), + CANNOT_MOVE_FOLDER_INTO_SUBFOLDER_OF_ITSELF(2043), + FILE_OR_FOLDER_NOT_FOUND(2055), + NO_FILE_UPLOAD_DETECTED(2088), + INVALID_ACCESS_TOKEN(2094), + ACCESS_TOKEN_REVOKED(2095), + TRANSFER_OVER_QUOTA(2097), + TARGET_FOLDER_DOES_NOT_EXIST(2208), + TOO_MANY_LOGIN_TRIES_FROM_IP(4000), + INTERNAL_ERROR(5000), + INTERNAL_UPLOAD_ERROR(5001); + + private final int value; + + PCloudApiErrorCodes(final int newValue) { + value = newValue; + } + + public int getValue() { + return value; + } + } + + public static boolean isCloudNodeAlreadyExistsException(int errorCode) { + return errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue(); + } + + public static boolean isFatalBackendException(int errorCode) { + return errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() + || errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() + || errorCode == PCloudApiErrorCodes.UPLOAD_NOT_FOUND.getValue() + || errorCode == PCloudApiErrorCodes.TRANSFER_NOT_FOUND.getValue(); + } + + public static boolean isForbiddenException(int errorCode) { + return errorCode == PCloudApiErrorCodes.ACCESS_DENIED.getValue(); + } + + public static boolean isNetworkConnectionException(int errorCode) { + return errorCode == PCloudApiErrorCodes.CONNECTION_BROKE.getValue(); + } + + public static boolean isNoSuchCloudFileException(int errorCode) { + return errorCode == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue() + || errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue() + || errorCode == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue(); + } + + public static boolean isWrongCredentialsException(int errorCode) { + return errorCode == PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue() + || errorCode == PCloudApiErrorCodes.ACCESS_TOKEN_REVOKED.getValue(); + } + + public static boolean isUnauthorizedException(int errorCode) { + return errorCode == PCloudApiErrorCodes.LOGIN_FAILED.getValue() + || errorCode == PCloudApiErrorCodes.LOGIN_REQUIRED.getValue() + || errorCode == PCloudApiErrorCodes.TOO_MANY_LOGIN_TRIES_FROM_IP.getValue(); + } + +} \ No newline at end of file diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java deleted file mode 100644 index 710029a9..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiErrorCodes.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.cryptomator.data.cloud.pcloud; - -public enum PCloudApiErrorCodes { - LOGIN_REQUIRED(1000), - NO_FULL_PATH_OR_NAME_FOLDER_ID_PROVIDED(1001), - NO_FULL_PATH_OR_FOLDER_ID_PROVIDED(1002), - NO_DESTINATION_PROVIDED(1016), - INVALID_FOLDER_ID(1017), - INVALID_DESTINATION(1037), - PROVIDE_URL(1040), - LOGIN_FAILED(2000), - INVALID_FILE_OR_FOLDER_NAME(2001), - COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST(2002), - ACCESS_DENIED(2003), - FILE_OR_FOLDER_ALREADY_EXISTS(2004), - DIRECTORY_DOES_NOT_EXIST(2005), - FOLDER_NOT_EMPTY(2006), - CANNOT_DELETE_ROOT_FOLDER(2007), - USER_OVER_QUOTA(2008), - FILE_NOT_FOUND(2009), - SHARED_FOLDER_IN_SHARED_FOLDER(2023), - ACTIVE_SHARES_OR_SHAREREQUESTS_PRESENT(2028), - CANNOT_RENAME_ROOT_FOLDER(2042), - CANNOT_MOVE_FOLDER_INTO_SUBFOLDER_OF_ITSELF(2043), - FILE_OR_FOLDER_NOT_FOUND(2055), - INVALID_ACCESS_TOKEN(2094), - ACCESS_TOKEN_REVOKED(2095), - TOO_MANY_LOGIN_TRIES_FROM_IP(4000), - INTERNAL_ERROR(5000), - INTERNAL_UPLOAD_ERROR(5001); - - private final int value; - - PCloudApiErrorCodes(final int newValue) { - value = newValue; - } - - public int getValue() { return value; } -} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java index 88dd65f0..99388bc5 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java @@ -50,7 +50,8 @@ class PCloudContentRepository extends InterceptingCloudContentRepository size) throws BackendException { - return cloud.file(parent, name, size); + try { + return cloud.file(parent, name, size); + } catch(IOException ex) { + throw new FatalBackendException(ex); + } } @Override - public PCloudFolder folder(PCloudFolder parent, String name) { - return cloud.folder(parent, name); + public PCloudFolder folder(PCloudFolder parent, String name) throws BackendException { + try { + return cloud.folder(parent, name); + } catch(IOException ex) { + throw new FatalBackendException(ex); + } } @Override public boolean exists(PCloudNode node) throws BackendException { try { return cloud.exists(node); - } catch (ApiError|IOException e) { + } catch (IOException e) { throw new FatalBackendException(e); } } @@ -101,12 +118,7 @@ class PCloudContentRepository extends InterceptingCloudContentRepository list(PCloudFolder folder) throws BackendException { try { return cloud.list(folder); - } catch (ApiError | IOException e) { - if (e instanceof ApiError) { - if (((ApiError) e).errorCode() == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue()) { - throw new NoSuchCloudFileException(); - } - } + } catch (IOException e) { throw new FatalBackendException(e); } } @@ -115,11 +127,7 @@ class PCloudContentRepository extends InterceptingCloudContentRepository progressAware, boolean replace, long size) throws BackendException { try { return cloud.write(uploadFile, data, progressAware, replace, size); - } catch (ApiError | IOException e) { - if (e instanceof ApiError && ((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) { - throw new NoSuchCloudFileException(uploadFile.getName()); - } + } catch (IOException e) { throw new FatalBackendException(e); } } @@ -174,10 +163,7 @@ class PCloudContentRepository extends InterceptingCloudContentRepository encryptedTmpFile, OutputStream data, ProgressAware progressAware) throws BackendException { try { cloud.read(file, data, progressAware); - } catch (ApiError | IOException e) { - if (e instanceof ApiError && ((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) { - throw new NoSuchCloudFileException(file.getName()); - } + } catch (IOException e) { throw new FatalBackendException(e); } } @@ -186,12 +172,7 @@ class PCloudContentRepository extends InterceptingCloudContentRepository findEntry(Long folderId, String name, boolean isFolder) { + private Optional findEntry(Long folderId, String name, boolean isFolder) throws IOException, BackendException { try { RemoteFolder remoteFolder = client().listFolder(folderId).execute(); for (RemoteEntry remoteEntry : remoteFolder.children()) { @@ -97,22 +106,19 @@ class PCloudImpl { } } return Optional.empty(); - } catch(ApiError | IOException ex) { - if (ex instanceof ApiError) { - int errorCode = ((ApiError)ex).errorCode(); - if (errorCode == PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue() || errorCode == PCloudApiErrorCodes.ACCESS_TOKEN_REVOKED.getValue()) { - throw new WrongCredentialsException(cloud); - } - } + } catch(ApiError ex) { + Set ignoredErrorCodes = new HashSet<>(); + ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue()); + handleApiError(ex, ignoredErrorCodes); return Optional.empty(); } } - public PCloudFile file(PCloudFolder parent, String name) { + public PCloudFile file(PCloudFolder parent, String name) throws BackendException, IOException { return file(parent, name, Optional.empty()); } - public PCloudFile file(PCloudFolder parent, String name, Optional size) { + public PCloudFile file(PCloudFolder parent, String name, Optional size) throws BackendException, IOException { if (parent.getId() == null) { return PCloudNodeFactory.file(parent, name, size); } @@ -131,7 +137,7 @@ class PCloudImpl { return PCloudNodeFactory.file(parent, name, size); } - public PCloudFolder folder(PCloudFolder parent, String name) { + public PCloudFolder folder(PCloudFolder parent, String name) throws IOException, BackendException { if (parent.getId() == null) { return PCloudNodeFactory.folder(parent, name); } @@ -149,7 +155,7 @@ class PCloudImpl { return PCloudNodeFactory.folder(parent, name); } - public boolean exists(PCloudNode node) throws ApiError, IOException { + public boolean exists(PCloudNode node) throws IOException, BackendException { try { if (node instanceof PCloudFolder) { RemoteFolder remoteFolder = client().listFolder(node.getPath()).execute(); @@ -159,38 +165,42 @@ class PCloudImpl { idCache.add(PCloudNodeFactory.file(node.getParent(), remoteFile)); } return true; - } catch (ApiError e) { - if (e.errorCode() == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() - || e.errorCode() == PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue() - || e.errorCode() == PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue() - || e.errorCode() == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()) { - return false; - } - throw e; + } catch (ApiError ex) { + Set ignoredErrorCodes = new HashSet<>(); + ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue()); + ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue()); + ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue()); + ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()); + handleApiError(ex, ignoredErrorCodes); + return false; } } - public List list(PCloudFolder folder) throws ApiError, IOException { + public List list(PCloudFolder folder) throws IOException, BackendException { List result = new ArrayList<>(); Long folderId = folder.getId(); RemoteFolder listFolderResult; - if (folderId == null) { - listFolderResult = client().listFolder(folder.getPath()).execute(); - } else { - listFolderResult = client() // - .listFolder(folder.getId()) // - .execute(); + try { + if (folderId == null) { + listFolderResult = client().listFolder(folder.getPath()).execute(); + } else { + listFolderResult = client() // + .listFolder(folder.getId()) // + .execute(); + } + List entryMetadata = listFolderResult.children(); + for (RemoteEntry metadata : entryMetadata) { + result.add(idCache.cache(PCloudNodeFactory.from(folder, metadata))); + } + return result; + } catch(ApiError ex) { + handleApiError(ex); + throw new FatalBackendException(ex); } - - List entryMetadata = listFolderResult.children(); - for (RemoteEntry metadata : entryMetadata) { - result.add(idCache.cache(PCloudNodeFactory.from(folder, metadata))); - } - return result; } - public PCloudFolder create(PCloudFolder folder) throws ApiError, IOException { + public PCloudFolder create(PCloudFolder folder) throws IOException, BackendException { if (folder.getParent().getId() == null) { folder = new PCloudFolder( // create(folder.getParent()), // @@ -199,38 +209,48 @@ class PCloudImpl { ); } - RemoteFolder createdFolder = client() // - .createFolder(folder.getParent().getId(), folder.getName()) // - .execute(); - return idCache.cache( // - PCloudNodeFactory.folder(folder.getParent(), createdFolder)); + try { + RemoteFolder createdFolder = client() // + .createFolder(folder.getParent().getId(), folder.getName()) // + .execute(); + return idCache.cache( // + PCloudNodeFactory.folder(folder.getParent(), createdFolder)); + } catch (ApiError ex) { + handleApiError(ex); + throw new FatalBackendException(ex); + } } - public PCloudNode move(PCloudNode source, PCloudNode target) throws ApiError, BackendException, IOException { + public PCloudNode move(PCloudNode source, PCloudNode target) throws IOException, BackendException { if (exists(target)) { throw new CloudNodeAlreadyExistsException(target.getName()); } RemoteEntry relocationResult; - if (source instanceof PCloudFolder) { - relocationResult = client().moveFolder(source.getId(), target.getParent().getId()).execute(); - if (!relocationResult.name().equals(target.getName())) { - relocationResult = client().renameFolder(relocationResult.asFolder(), target.getName()).execute(); + try { + if (source instanceof PCloudFolder) { + relocationResult = client().moveFolder(source.getId(), target.getParent().getId()).execute(); + if (!relocationResult.name().equals(target.getName())) { + relocationResult = client().renameFolder(relocationResult.asFolder(), target.getName()).execute(); + } + } else { + relocationResult = client().moveFile(source.getId(), target.getParent().getId()).execute(); + if (!relocationResult.name().equals(target.getName())) { + relocationResult = client().renameFile(relocationResult.asFile(), target.getName()).execute(); + } } - } else { - relocationResult = client().moveFile(source.getId(), target.getParent().getId()).execute(); - if (!relocationResult.name().equals(target.getName())) { - relocationResult = client().renameFile(relocationResult.asFile(), target.getName()).execute(); - } - } - idCache.remove(source); - return idCache.cache(PCloudNodeFactory.from(target.getParent(), relocationResult)); + idCache.remove(source); + return idCache.cache(PCloudNodeFactory.from(target.getParent(), relocationResult)); + } catch(ApiError ex) { + handleApiError(ex); + throw new FatalBackendException(ex); + } } public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) - throws ApiError, BackendException, IOException { + throws IOException, BackendException { if (!replace && exists(file)) { throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false"); } @@ -253,7 +273,7 @@ class PCloudImpl { } private RemoteFile uploadFile(final PCloudFile file, DataSource data, final ProgressAware progressAware, UploadOptions uploadOptions, final long size) // - throws ApiError, IOException { + throws IOException, BackendException { ProgressListener listener = (done, total) -> progressAware.onProgress( // progress(UploadState.upload(file)) // .between(0) // @@ -280,19 +300,23 @@ class PCloudImpl { } String filename = file.getName(); - String encodedFilename = URLEncoder.encode(filename, "UTF-8"); + String encodedFilename = URLEncoder.encode(filename, UTF_8); - RemoteFile newFile = client() // - .createFile(parentFolderId, encodedFilename, pCloudDataSource, new Date(), listener, uploadOptions) // - .execute(); - if (!filename.equals(encodedFilename)) { - return client().renameFile(newFile.fileId(), filename).execute(); + try { + RemoteFile newFile = client() // + .createFile(parentFolderId, encodedFilename, pCloudDataSource, new Date(), listener, uploadOptions) // + .execute(); + if (!filename.equals(encodedFilename)) { + return client().renameFile(newFile.fileId(), filename).execute(); + } + return newFile; + } catch (ApiError ex) { + handleApiError(ex); + throw new FatalBackendException(ex); } - - return newFile; } - public void read(PCloudFile file, OutputStream data, final ProgressAware progressAware) throws ApiError, IOException { + public void read(PCloudFile file, OutputStream data, final ProgressAware progressAware) throws IOException, BackendException { progressAware.onProgress(Progress.started(DownloadState.download(file))); Long fileId = file.getId(); @@ -300,41 +324,83 @@ class PCloudImpl { fileId = idCache.get(file.getPath()).getId(); } - FileLink fileLink = client().createFileLink(fileId, DownloadOptions.DEFAULT).execute(); + try { + FileLink fileLink = client().createFileLink(fileId, DownloadOptions.DEFAULT).execute(); - ProgressListener listener = (done, total) -> progressAware.onProgress( // - progress(DownloadState.download(file)) // - .between(0) // - .and(file.getSize().orElse(Long.MAX_VALUE)) // - .withValue(done)); + ProgressListener listener = (done, total) -> progressAware.onProgress( // + progress(DownloadState.download(file)) // + .between(0) // + .and(file.getSize().orElse(Long.MAX_VALUE)) // + .withValue(done)); - DataSink sink = new DataSink() { - @Override - public void readAll(BufferedSource source) throws IOException { - CopyStream.copyStreamToStream(source.inputStream(), data); - } - }; + DataSink sink = new DataSink() { + @Override + public void readAll(BufferedSource source) throws IOException { + CopyStream.copyStreamToStream(source.inputStream(), data); + } + }; - client().download(fileLink, sink, listener).execute(); + client().download(fileLink, sink, listener).execute(); - progressAware.onProgress(Progress.completed(DownloadState.download(file))); - } - - public void delete(PCloudNode node) throws ApiError, IOException { - if (node instanceof PCloudFolder) { - client() // - .deleteFolder(node.getId(), true).execute(); - } else { - client() // - .deleteFile(node.getId()).execute(); + progressAware.onProgress(Progress.completed(DownloadState.download(file))); + } catch(ApiError ex) { + handleApiError(ex); } - idCache.remove(node); } - public String currentAccount() throws ApiError, IOException { - UserInfo currentAccount = client() // - .getUserInfo() // - .execute(); - return currentAccount.email(); + public void delete(PCloudNode node) throws IOException, BackendException { + try { + if (node instanceof PCloudFolder) { + client() // + .deleteFolder(node.getId(), true).execute(); + } else { + client() // + .deleteFile(node.getId()).execute(); + } + idCache.remove(node); + } catch(ApiError ex) { + handleApiError(ex); + } + } + + public String currentAccount() throws IOException, BackendException { + try { + UserInfo currentAccount = client() // + .getUserInfo() // + .execute(); + return currentAccount.email(); + } catch(ApiError ex) { + handleApiError(ex); + throw new FatalBackendException(ex); + } + } + + private void handleApiError(ApiError ex) throws BackendException { + handleApiError(ex, null); + } + + private void handleApiError(ApiError ex, Set errorCodes) throws BackendException { + handleApiError(ex, errorCodes, null); + } + + private void handleApiError(ApiError ex, Set errorCodes, String name) throws BackendException { + if (errorCodes == null || !errorCodes.contains(ex.errorCode())) { + int errorCode = ex.errorCode(); + if (PCloudApiError.isCloudNodeAlreadyExistsException(errorCode)) { + throw new CloudNodeAlreadyExistsException(name); + } else if (PCloudApiError.isForbiddenException(errorCode)){ + throw new ForbiddenException(); + } else if (PCloudApiError.isNetworkConnectionException(errorCode)) { + throw new NetworkConnectionException(ex); + } else if (PCloudApiError.isNoSuchCloudFileException(errorCode)) { + throw new NoSuchCloudFileException(name); + } else if (PCloudApiError.isWrongCredentialsException(errorCode)) { + throw new WrongCredentialsException(cloud); + } else if (PCloudApiError.isUnauthorizedException(errorCode)) { + throw new UnauthorizedException(); + } else { + throw new FatalBackendException(ex); + } + } } } From 59040737768ed9ff7e5e5cf279c39128679296ba Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 19 Mar 2021 14:17:11 +0100 Subject: [PATCH 67/93] fix: remove unused imports --- .../cryptomator/data/cloud/pcloud/PCloudContentRepository.java | 2 -- .../main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 1 - 2 files changed, 3 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java index 99388bc5..fabd89bd 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java @@ -7,10 +7,8 @@ import com.pcloud.sdk.ApiError; import org.cryptomator.data.cloud.InterceptingCloudContentRepository; import org.cryptomator.domain.PCloud; import org.cryptomator.domain.exception.BackendException; -import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; import org.cryptomator.domain.exception.FatalBackendException; import org.cryptomator.domain.exception.NetworkConnectionException; -import org.cryptomator.domain.exception.NoSuchCloudFileException; import org.cryptomator.domain.exception.authentication.WrongCredentialsException; import org.cryptomator.domain.repository.CloudContentRepository; import org.cryptomator.domain.usecases.ProgressAware; diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 7b73058a..0f09bc0d 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -41,7 +41,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import okhttp3.internal.concurrent.TaskRunner; import okio.BufferedSink; import okio.BufferedSource; import okio.Okio; From 44c5029651258abe19be80061423bc6d4081fc2c Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 19 Mar 2021 14:20:02 +0100 Subject: [PATCH 68/93] fix(rebase): use proper setter for Url --- .../org/cryptomator/data/db/mappers/CloudEntityMapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java index 20be3e7a..5644d6c6 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java @@ -52,7 +52,7 @@ public class CloudEntityMapper extends EntityMapper { case PCLOUD: return aPCloud() // .withId(entity.getId()) // - .withUrl(entity.getWebdavUrl()) // + .withUrl(entity.getUrl()) // .withAccessToken(entity.getAccessToken()) // .withUsername(entity.getUsername()) // .build(); @@ -93,7 +93,7 @@ public class CloudEntityMapper extends EntityMapper { break; case PCLOUD: result.setAccessToken(((PCloud) domainObject).accessToken()); - result.setWebdavUrl(((PCloud) domainObject).url()); + result.setUrl(((PCloud) domainObject).url()); result.setUsername(((PCloud) domainObject).username()); break; case LOCAL: From 2158e2a0d47fd9cb3b742d74583a97a4cea68fd1 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 19 Mar 2021 15:00:40 +0100 Subject: [PATCH 69/93] fix: move from `stat` to `loadFile` --- .../main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 0f09bc0d..fd9495a6 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -160,7 +160,7 @@ class PCloudImpl { RemoteFolder remoteFolder = client().listFolder(node.getPath()).execute(); idCache.add(PCloudNodeFactory.folder(node.getParent(), remoteFolder)); } else { - RemoteFile remoteFile = client().stat(node.getPath()).execute(); + RemoteFile remoteFile = client().loadFile(node.getPath()).execute(); idCache.add(PCloudNodeFactory.file(node.getParent(), remoteFile)); } return true; From 81bde173f1567df61b6a39ab0573654561750417 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Sun, 21 Mar 2021 10:47:01 +0100 Subject: [PATCH 70/93] fix: improve exception handling for read() --- .../cryptomator/data/cloud/pcloud/PCloudImpl.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index fd9495a6..96d016e3 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -91,6 +91,9 @@ class PCloudImpl { } private Optional findEntry(Long folderId, String name, boolean isFolder) throws IOException, BackendException { + if (folderId == null) { + throw new NoSuchCloudFileException(); + } try { RemoteFolder remoteFolder = client().listFolder(folderId).execute(); for (RemoteEntry remoteEntry : remoteFolder.children()) { @@ -108,6 +111,8 @@ class PCloudImpl { } catch(ApiError ex) { Set ignoredErrorCodes = new HashSet<>(); ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue()); + ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()); + ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()); handleApiError(ex, ignoredErrorCodes); return Optional.empty(); } @@ -320,7 +325,15 @@ class PCloudImpl { Long fileId = file.getId(); if (fileId == null) { - fileId = idCache.get(file.getPath()).getId(); + PCloudIdCache.NodeInfo nodeInfo = idCache.get(file.getPath()); + if (nodeInfo != null) { + fileId = nodeInfo.getId(); + } else { + Optional remoteEntryOptional = findEntry(file.getParent().getId(), file.getName(), false); + if (remoteEntryOptional.isPresent()) { + fileId = remoteEntryOptional.get().asFile().fileId(); + } + } } try { From a491dd47668351bdeef27cf84b635116cb22c152 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Sun, 21 Mar 2021 15:10:04 +0100 Subject: [PATCH 71/93] fix: use correct access token --- .../presentation/presenter/CloudConnectionListPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index 8013af11..9efa046a 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -231,7 +231,7 @@ class CloudConnectionListPresenter @Inject constructor( // }?.let { it as PCloud saveCloud(PCloud.aCopyOf(it) // .withUrl(cloud.url()) - .withAccessToken(it.accessToken()) + .withAccessToken(cloud.accessToken()) .build()) } ?: saveCloud(cloud) } From 8c030a5f9c89bc0f54891976983de2af57dc0649 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Mon, 22 Mar 2021 14:07:31 +0100 Subject: [PATCH 72/93] fix: throw NoSuchCloudFileException if fileId is missing --- .../main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 96d016e3..fbaec77e 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -332,6 +332,8 @@ class PCloudImpl { Optional remoteEntryOptional = findEntry(file.getParent().getId(), file.getName(), false); if (remoteEntryOptional.isPresent()) { fileId = remoteEntryOptional.get().asFile().fileId(); + } else { + throw new NoSuchCloudFileException(file.getName()); } } } From b97a4750c697bfbf06ba749d03b6544a69bae669 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 23 Mar 2021 09:51:29 +0100 Subject: [PATCH 73/93] feat: implement file cache Due to the way the pCloud revision handling is implemented every download will actually be a cache miss even if the file is small enough and didn't change since last download. Therefore, we have to implement our own file cache. --- .../cloud/pcloud/PCloudContentRepository.java | 2 +- .../data/cloud/pcloud/PCloudImpl.java | 106 +++++++++++++++--- 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java index fabd89bd..4697e15c 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java @@ -160,7 +160,7 @@ class PCloudContentRepository extends InterceptingCloudContentRepository encryptedTmpFile, OutputStream data, ProgressAware progressAware) throws BackendException { try { - cloud.read(file, data, progressAware); + cloud.read(file, encryptedTmpFile, data, progressAware); } catch (IOException e) { throw new FatalBackendException(e); } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index fbaec77e..6034b3a8 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -2,6 +2,8 @@ package org.cryptomator.data.cloud.pcloud; import android.content.Context; +import com.google.api.services.drive.model.Revision; +import com.google.api.services.drive.model.RevisionList; import com.pcloud.sdk.ApiClient; import com.pcloud.sdk.ApiError; import com.pcloud.sdk.DataSink; @@ -13,6 +15,7 @@ import com.pcloud.sdk.RemoteFile; import com.pcloud.sdk.RemoteFolder; import com.pcloud.sdk.UploadOptions; import com.pcloud.sdk.UserInfo; +import com.tomclaw.cache.DiskLruCache; import org.cryptomator.data.util.CopyStream; import org.cryptomator.domain.PCloud; @@ -31,7 +34,10 @@ import org.cryptomator.domain.usecases.cloud.DownloadState; import org.cryptomator.domain.usecases.cloud.Progress; import org.cryptomator.domain.usecases.cloud.UploadState; import org.cryptomator.util.Optional; +import org.cryptomator.util.SharedPreferencesHandler; +import org.cryptomator.util.file.LruFileCacheUtil; +import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URLEncoder; @@ -45,8 +51,13 @@ import okio.BufferedSink; import okio.BufferedSource; import okio.Okio; import okio.Source; +import timber.log.Timber; import static org.cryptomator.domain.usecases.cloud.Progress.progress; +import static org.cryptomator.util.file.LruFileCacheUtil.Cache.GOOGLE_DRIVE; +import static org.cryptomator.util.file.LruFileCacheUtil.Cache.PCLOUD; +import static org.cryptomator.util.file.LruFileCacheUtil.retrieveFromLruCache; +import static org.cryptomator.util.file.LruFileCacheUtil.storeToLruCache; class PCloudImpl { @@ -57,6 +68,9 @@ class PCloudImpl { private final RootPCloudFolder root; private final Context context; + private final SharedPreferencesHandler sharedPreferencesHandler; + private DiskLruCache diskLruCache; + private final String UTF_8 = "UTF-8"; PCloudImpl(Context context, PCloud cloud, PCloudIdCache idCache) { @@ -68,6 +82,7 @@ class PCloudImpl { this.cloud = cloud; this.idCache = idCache; this.root = new RootPCloudFolder(cloud); + this.sharedPreferencesHandler = new SharedPreferencesHandler(context); } private ApiClient client() { @@ -320,24 +335,65 @@ class PCloudImpl { } } - public void read(PCloudFile file, OutputStream data, final ProgressAware progressAware) throws IOException, BackendException { + public void read(PCloudFile file, Optional encryptedTmpFile, OutputStream data, final ProgressAware progressAware) throws IOException, BackendException { progressAware.onProgress(Progress.started(DownloadState.download(file))); Long fileId = file.getId(); if (fileId == null) { - PCloudIdCache.NodeInfo nodeInfo = idCache.get(file.getPath()); - if (nodeInfo != null) { - fileId = nodeInfo.getId(); - } else { - Optional remoteEntryOptional = findEntry(file.getParent().getId(), file.getName(), false); - if (remoteEntryOptional.isPresent()) { - fileId = remoteEntryOptional.get().asFile().fileId(); - } else { - throw new NoSuchCloudFileException(file.getName()); - } - } + PCloudIdCache.NodeInfo nodeInfo = idCache.get(file.getPath()); + if (nodeInfo != null) { + fileId = nodeInfo.getId(); + } } + RemoteFile remoteFile = null; + if (fileId == null) { + Optional remoteEntryOptional = findEntry(file.getParent().getId(), file.getName(), false); + if (remoteEntryOptional.isPresent()) { + remoteFile = remoteEntryOptional.get().asFile(); + fileId = remoteFile.fileId(); + } else { + throw new NoSuchCloudFileException(file.getName()); + } + } + + Optional cacheKey = Optional.empty(); + Optional cacheFile = Optional.empty(); + + if (sharedPreferencesHandler.useLruCache() && createLruCache(sharedPreferencesHandler.lruCacheSize())) { + if (remoteFile == null) { + try { + remoteFile = client().loadFile(fileId).execute().asFile(); + cacheKey = Optional.of(remoteFile.fileId() + remoteFile.hash()); + } catch(ApiError ex) { + handleApiError(ex); + } + } + + File cachedFile = diskLruCache.get(cacheKey.get()); + cacheFile = cachedFile != null ? Optional.of(cachedFile) : Optional.empty(); + } + + if (sharedPreferencesHandler.useLruCache() && cacheFile.isPresent()) { + try { + retrieveFromLruCache(cacheFile.get(), data); + } catch (IOException e) { + Timber.tag("PCloudImpl").w(e, "Error while retrieving content from Cache, get from web request"); + writeData(file, fileId, data, encryptedTmpFile, cacheKey, progressAware); + } + } else { + writeData(file, fileId, data, encryptedTmpFile, cacheKey, progressAware); + } + + progressAware.onProgress(Progress.completed(DownloadState.download(file))); + } + + private void writeData(final PCloudFile file, // + final long fileId, // + final OutputStream data, // + final Optional encryptedTmpFile, // + final Optional cacheKey, // + final ProgressAware progressAware) throws IOException, BackendException { try { FileLink fileLink = client().createFileLink(fileId, DownloadOptions.DEFAULT).execute(); @@ -349,17 +405,24 @@ class PCloudImpl { DataSink sink = new DataSink() { @Override - public void readAll(BufferedSource source) throws IOException { + public void readAll(BufferedSource source) { CopyStream.copyStreamToStream(source.inputStream(), data); } }; client().download(fileLink, sink, listener).execute(); - - progressAware.onProgress(Progress.completed(DownloadState.download(file))); } catch(ApiError ex) { handleApiError(ex); } + + if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) { + try { + storeToLruCache(diskLruCache, cacheKey.get(), encryptedTmpFile.get()); + } catch (IOException e) { + Timber.tag("PCloudImpl").e(e, "Failed to write downloaded file in LRU cache"); + } + } + } public void delete(PCloudNode node) throws IOException, BackendException { @@ -389,6 +452,19 @@ class PCloudImpl { } } + private boolean createLruCache(int cacheSize) { + if (diskLruCache == null) { + try { + diskLruCache = DiskLruCache.create(new LruFileCacheUtil(context).resolve(PCLOUD), cacheSize); + } catch (IOException e) { + Timber.tag("PCloudImpl").e(e, "Failed to setup LRU cache"); + return false; + } + } + + return true; + } + private void handleApiError(ApiError ex) throws BackendException { handleApiError(ex, null); } From c5c139f962d671e96b8981b80e367e8106502134 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 23 Mar 2021 09:56:44 +0100 Subject: [PATCH 74/93] fix: once again remove unused imports... --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 6034b3a8..d432139d 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -2,8 +2,6 @@ package org.cryptomator.data.cloud.pcloud; import android.content.Context; -import com.google.api.services.drive.model.Revision; -import com.google.api.services.drive.model.RevisionList; import com.pcloud.sdk.ApiClient; import com.pcloud.sdk.ApiError; import com.pcloud.sdk.DataSink; @@ -54,7 +52,6 @@ import okio.Source; import timber.log.Timber; import static org.cryptomator.domain.usecases.cloud.Progress.progress; -import static org.cryptomator.util.file.LruFileCacheUtil.Cache.GOOGLE_DRIVE; import static org.cryptomator.util.file.LruFileCacheUtil.Cache.PCLOUD; import static org.cryptomator.util.file.LruFileCacheUtil.retrieveFromLruCache; import static org.cryptomator.util.file.LruFileCacheUtil.storeToLruCache; From 20939f9ba085d10ee95cb42ce1867f2a49a86cb9 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 23 Mar 2021 10:42:05 +0100 Subject: [PATCH 75/93] fix: rename writeData to writeToData --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index d432139d..6d809135 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -376,16 +376,16 @@ class PCloudImpl { retrieveFromLruCache(cacheFile.get(), data); } catch (IOException e) { Timber.tag("PCloudImpl").w(e, "Error while retrieving content from Cache, get from web request"); - writeData(file, fileId, data, encryptedTmpFile, cacheKey, progressAware); + writeToData(file, fileId, data, encryptedTmpFile, cacheKey, progressAware); } } else { - writeData(file, fileId, data, encryptedTmpFile, cacheKey, progressAware); + writeToData(file, fileId, data, encryptedTmpFile, cacheKey, progressAware); } progressAware.onProgress(Progress.completed(DownloadState.download(file))); } - private void writeData(final PCloudFile file, // + private void writeToData(final PCloudFile file, // final long fileId, // final OutputStream data, // final Optional encryptedTmpFile, // From d3bb9d30aa177efe495250c9ecf8e3c081e956e0 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 23 Mar 2021 12:43:08 +0100 Subject: [PATCH 76/93] fix: remove cache for OkHttpClient --- .../data/cloud/pcloud/PCloudClientFactory.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java index 019d8552..f0a0b535 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudClientFactory.java @@ -7,11 +7,8 @@ import com.pcloud.sdk.Authenticators; import com.pcloud.sdk.PCloudSdk; import org.cryptomator.data.cloud.okhttplogging.HttpLoggingInterceptor; -import org.cryptomator.util.SharedPreferencesHandler; import org.cryptomator.util.crypto.CredentialCryptor; -import org.cryptomator.util.file.LruFileCacheUtil; -import okhttp3.Cache; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import timber.log.Timber; @@ -30,13 +27,12 @@ class PCloudClientFactory { public ApiClient getClient(String accessToken, String url, Context context) { if (apiClient == null) { - final SharedPreferencesHandler sharedPreferencesHandler = new SharedPreferencesHandler(context); - apiClient = createApiClient(accessToken, url, context, sharedPreferencesHandler.useLruCache(), sharedPreferencesHandler.lruCacheSize()); + apiClient = createApiClient(accessToken, url, context); } return apiClient; } - private ApiClient createApiClient(String accessToken, String url, Context context, boolean useLruCache, int lruCacheSize) { + private ApiClient createApiClient(String accessToken, String url, Context context) { OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient() // .newBuilder() // .connectTimeout(CONNECTION.getTimeout(), CONNECTION.getUnit()) // @@ -44,10 +40,6 @@ class PCloudClientFactory { .writeTimeout(WRITE.getTimeout(), WRITE.getUnit()) // .addInterceptor(httpLoggingInterceptor(context)); //; - if (useLruCache) { - okHttpClientBuilder.cache(new Cache(new LruFileCacheUtil(context).resolve(LruFileCacheUtil.Cache.PCLOUD), lruCacheSize)); - } - OkHttpClient okHttpClient = okHttpClientBuilder.build(); return PCloudSdk.newClientBuilder().authenticator(Authenticators.newOAuthAuthenticator(decrypt(accessToken, context))).withClient(okHttpClient).apiHost(url).create(); From 6ccee05851c299afbcc8deed9d37584222ea3d7c Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 23 Mar 2021 15:07:46 +0100 Subject: [PATCH 77/93] fix: instantiate separate idCache for each PCloud instance --- .../data/cloud/pcloud/PCloudContentRepository.java | 8 ++++---- .../data/cloud/pcloud/PCloudContentRepositoryFactory.java | 6 ++---- .../org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 4 ++-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java index 4697e15c..9c93ddcd 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java @@ -28,8 +28,8 @@ class PCloudContentRepository extends InterceptingCloudContentRepository Date: Tue, 23 Mar 2021 15:31:33 +0100 Subject: [PATCH 78/93] fix: actually verify configurationMatches --- domain/src/main/java/org/cryptomator/domain/PCloud.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/domain/src/main/java/org/cryptomator/domain/PCloud.java b/domain/src/main/java/org/cryptomator/domain/PCloud.java index 9803034f..d81a2cdc 100644 --- a/domain/src/main/java/org/cryptomator/domain/PCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/PCloud.java @@ -52,9 +52,14 @@ public class PCloud implements Cloud { @Override public boolean configurationMatches(Cloud cloud) { - return true; + return cloud instanceof PCloud && configurationMatches((PCloud) cloud); } + private boolean configurationMatches(PCloud cloud) { + return url.equals(cloud.url) && username.equals(cloud.username); + } + + @Override public boolean predefined() { return false; From 9b93fb9623bc512e78cc9489ab96c1e0bf6bdf23 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Wed, 24 Mar 2021 10:50:27 +0100 Subject: [PATCH 79/93] feat: remove idCache and switch to loading with paths --- .../data/cloud/pcloud/PCloudFile.java | 9 +- .../data/cloud/pcloud/PCloudFolder.java | 11 +- .../data/cloud/pcloud/PCloudIdCache.java | 77 -------- .../data/cloud/pcloud/PCloudImpl.java | 168 +++--------------- .../data/cloud/pcloud/PCloudNode.java | 2 - .../data/cloud/pcloud/PCloudNodeFactory.java | 16 +- .../data/cloud/pcloud/RootPCloudFolder.java | 4 +- 7 files changed, 40 insertions(+), 247 deletions(-) delete mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java index 787baa93..b245a4e7 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFile.java @@ -11,15 +11,13 @@ class PCloudFile implements CloudFile, PCloudNode { private final PCloudFolder parent; private final String name; private final String path; - private final Long fileId; private final Optional size; private final Optional modified; - public PCloudFile(PCloudFolder parent, String name, String path, Long fileId, Optional size, Optional modified) { + public PCloudFile(PCloudFolder parent, String name, String path, Optional size, Optional modified) { this.parent = parent; this.name = name; this.path = path; - this.fileId = fileId; this.size = size; this.modified = modified; } @@ -39,11 +37,6 @@ class PCloudFile implements CloudFile, PCloudNode { return path; } - @Override - public Long getId() { - return fileId; - } - @Override public PCloudFolder getParent() { return parent; diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java index 7abc0496..2674ffd6 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudFolder.java @@ -8,11 +8,9 @@ class PCloudFolder implements CloudFolder, PCloudNode { private final PCloudFolder parent; private final String name; private final String path; - private final Long folderId; - public PCloudFolder(PCloudFolder parent, String name, String path, Long folderId) { + public PCloudFolder(PCloudFolder parent, String name, String path) { this.parent = parent; - this.folderId = folderId; this.name = name; this.path = path; } @@ -32,11 +30,6 @@ class PCloudFolder implements CloudFolder, PCloudNode { return path; } - @Override - public Long getId() { - return folderId; - } - @Override public PCloudFolder getParent() { return parent; @@ -44,6 +37,6 @@ class PCloudFolder implements CloudFolder, PCloudNode { @Override public PCloudFolder withCloud(Cloud cloud) { - return new PCloudFolder(parent.withCloud(cloud), name, path, folderId); + return new PCloudFolder(parent.withCloud(cloud), name, path); } } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java deleted file mode 100644 index 5862fedd..00000000 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.cryptomator.data.cloud.pcloud; - -import android.util.LruCache; - -import org.cryptomator.domain.CloudFolder; - -import javax.inject.Inject; - -class PCloudIdCache { - - private final LruCache cache; - - @Inject - PCloudIdCache() { - cache = new LruCache<>(1000); - } - - public NodeInfo get(String path) { - return cache.get(path); - } - - T cache(T value) { - add(value); - return value; - } - - public void add(PCloudNode node) { - add(node.getPath(), new NodeInfo(node)); - } - - private void add(String path, NodeInfo info) { - cache.put(path, info); - } - - public void remove(PCloudNode node) { - remove(node.getPath()); - } - - private void remove(String path) { - removeChildren(path); - cache.remove(path); - } - - private void removeChildren(String path) { - String prefix = path + '/'; - for (String key : cache.snapshot().keySet()) { - if (key.startsWith(prefix)) { - cache.remove(key); - } - } - } - - static class NodeInfo { - - private final Long id; - private final boolean isFolder; - - private NodeInfo(PCloudNode node) { - this(node.getId(), node instanceof CloudFolder); - } - - NodeInfo(Long id, boolean isFolder) { - this.id = id; - this.isFolder = isFolder; - } - - public Long getId() { - return id; - } - - public boolean isFolder() { - return isFolder; - } - - } - -} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 5fb60cba..3a00a36f 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -58,8 +58,6 @@ import static org.cryptomator.util.file.LruFileCacheUtil.storeToLruCache; class PCloudImpl { - private final PCloudIdCache idCache; - private final PCloudClientFactory clientFactory = new PCloudClientFactory(); private final PCloud cloud; private final RootPCloudFolder root; @@ -77,7 +75,6 @@ class PCloudImpl { this.context = context; this.cloud = cloud; - this.idCache = new PCloudIdCache(); this.root = new RootPCloudFolder(cloud); this.sharedPreferencesHandler = new SharedPreferencesHandler(context); } @@ -102,83 +99,24 @@ class PCloudImpl { return folder; } - private Optional findEntry(Long folderId, String name, boolean isFolder) throws IOException, BackendException { - if (folderId == null) { - throw new NoSuchCloudFileException(); - } - try { - RemoteFolder remoteFolder = client().listFolder(folderId).execute(); - for (RemoteEntry remoteEntry : remoteFolder.children()) { - if (isFolder) { - if (remoteEntry.isFolder() && remoteEntry.name().equals(name)) { - return Optional.of(remoteEntry); - } - } else { - if (remoteEntry.isFile() && remoteEntry.name().equals(name)) { - return Optional.of(remoteEntry); - } - } - } - return Optional.empty(); - } catch(ApiError ex) { - Set ignoredErrorCodes = new HashSet<>(); - ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue()); - ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()); - ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()); - handleApiError(ex, ignoredErrorCodes); - return Optional.empty(); - } - } - public PCloudFile file(PCloudFolder parent, String name) throws BackendException, IOException { return file(parent, name, Optional.empty()); } public PCloudFile file(PCloudFolder parent, String name, Optional size) throws BackendException, IOException { - if (parent.getId() == null) { - return PCloudNodeFactory.file(parent, name, size); - } - - String path = PCloudNodeFactory.getNodePath(parent, name); - PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); - if (nodeInfo != null && !nodeInfo.isFolder()) { - return PCloudNodeFactory.file(parent, name, size, path, nodeInfo.getId()); - } - - Optional file = findEntry(parent.getId(), name, false); - if (file.isPresent()) { - return idCache.cache(PCloudNodeFactory.file(parent, file.get().asFile())); - } - - return PCloudNodeFactory.file(parent, name, size); + return PCloudNodeFactory.file(parent, name, size, parent.getPath() + "/" + name); } public PCloudFolder folder(PCloudFolder parent, String name) throws IOException, BackendException { - if (parent.getId() == null) { - return PCloudNodeFactory.folder(parent, name); - } - - String path = PCloudNodeFactory.getNodePath(parent, name); - PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); - if (nodeInfo != null && nodeInfo.isFolder()) { - return PCloudNodeFactory.folder(parent, name, path, nodeInfo.getId()); - } - - Optional folder = findEntry(parent.getId(), name, true); - if (folder.isPresent()) { - return idCache.cache(PCloudNodeFactory.folder(parent, folder.get().asFolder())); - } - return PCloudNodeFactory.folder(parent, name); + return PCloudNodeFactory.folder(parent, name, parent.getPath() + "/" + name); } public boolean exists(PCloudNode node) throws IOException, BackendException { try { if (node instanceof PCloudFolder) { - RemoteFolder remoteFolder = client().listFolder(node.getPath()).execute(); - idCache.add(PCloudNodeFactory.folder(node.getParent(), remoteFolder)); + client().loadFolder(node.getPath()).execute(); } else { - RemoteFile remoteFile = client().loadFile(node.getPath()).execute(); - idCache.add(PCloudNodeFactory.file(node.getParent(), remoteFile)); + client().loadFile(node.getPath()).execute(); } return true; } catch (ApiError ex) { @@ -195,19 +133,11 @@ class PCloudImpl { public List list(PCloudFolder folder) throws IOException, BackendException { List result = new ArrayList<>(); - Long folderId = folder.getId(); - RemoteFolder listFolderResult; try { - if (folderId == null) { - listFolderResult = client().listFolder(folder.getPath()).execute(); - } else { - listFolderResult = client() // - .listFolder(folder.getId()) // - .execute(); - } + RemoteFolder listFolderResult = client().listFolder(folder.getPath()).execute(); List entryMetadata = listFolderResult.children(); for (RemoteEntry metadata : entryMetadata) { - result.add(idCache.cache(PCloudNodeFactory.from(folder, metadata))); + result.add(PCloudNodeFactory.from(folder, metadata)); } return result; } catch(ApiError ex) { @@ -217,20 +147,18 @@ class PCloudImpl { } public PCloudFolder create(PCloudFolder folder) throws IOException, BackendException { - if (folder.getParent().getId() == null) { + if (!exists(folder.getParent())) { folder = new PCloudFolder( // create(folder.getParent()), // - folder.getName(), folder.getPath(), folder.getId() // - // + folder.getName(), folder.getPath() // ); } try { RemoteFolder createdFolder = client() // - .createFolder(folder.getParent().getId(), folder.getName()) // + .createFolder(folder.getPath()) // .execute(); - return idCache.cache( // - PCloudNodeFactory.folder(folder.getParent(), createdFolder)); + return PCloudNodeFactory.folder(folder.getParent(), createdFolder); } catch (ApiError ex) { handleApiError(ex); throw new FatalBackendException(ex); @@ -242,23 +170,12 @@ class PCloudImpl { throw new CloudNodeAlreadyExistsException(target.getName()); } - RemoteEntry relocationResult; - try { if (source instanceof PCloudFolder) { - relocationResult = client().moveFolder(source.getId(), target.getParent().getId()).execute(); - if (!relocationResult.name().equals(target.getName())) { - relocationResult = client().renameFolder(relocationResult.asFolder(), target.getName()).execute(); - } + return PCloudNodeFactory.from(target.getParent(), client().moveFolder(source.getPath(), target.getPath()).execute()); } else { - relocationResult = client().moveFile(source.getId(), target.getParent().getId()).execute(); - if (!relocationResult.name().equals(target.getName())) { - relocationResult = client().renameFile(relocationResult.asFile(), target.getName()).execute(); - } + return PCloudNodeFactory.from(target.getParent(), client().moveFile(source.getPath(), target.getPath()).execute()); } - - idCache.remove(source); - return idCache.cache(PCloudNodeFactory.from(target.getParent(), relocationResult)); } catch(ApiError ex) { handleApiError(ex); throw new FatalBackendException(ex); @@ -271,13 +188,9 @@ class PCloudImpl { throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false"); } - if (file.getParent().getId() == null) { - throw new NoSuchCloudFileException(String.format("The parent folder of %s doesn't have a folderId. The file would remain in root folder", file.getPath())); - } - progressAware.onProgress(Progress.started(UploadState.upload(file))); UploadOptions uploadOptions = UploadOptions.DEFAULT; - if (file.getId() != null && replace) { + if (replace) { uploadOptions = UploadOptions.OVERRIDE_FILE; } @@ -285,7 +198,8 @@ class PCloudImpl { progressAware.onProgress(Progress.completed(UploadState.upload(file))); - return idCache.cache(PCloudNodeFactory.file(file.getParent(), uploadedFile)); + return PCloudNodeFactory.file(file.getParent(), uploadedFile); + } private RemoteFile uploadFile(final PCloudFile file, DataSource data, final ProgressAware progressAware, UploadOptions uploadOptions, final long size) // @@ -310,17 +224,12 @@ class PCloudImpl { } }; - Long parentFolderId = file.getParent().getId(); - if (parentFolderId == null) { - parentFolderId = idCache.get(file.getParent().getPath()).getId(); - } - String filename = file.getName(); String encodedFilename = URLEncoder.encode(filename, UTF_8); try { RemoteFile newFile = client() // - .createFile(parentFolderId, encodedFilename, pCloudDataSource, new Date(), listener, uploadOptions) // + .createFile(file.getParent().getPath(), encodedFilename, pCloudDataSource, new Date(), listener, uploadOptions) // .execute(); if (!filename.equals(encodedFilename)) { return client().renameFile(newFile.fileId(), filename).execute(); @@ -335,36 +244,17 @@ class PCloudImpl { public void read(PCloudFile file, Optional encryptedTmpFile, OutputStream data, final ProgressAware progressAware) throws IOException, BackendException { progressAware.onProgress(Progress.started(DownloadState.download(file))); - Long fileId = file.getId(); - if (fileId == null) { - PCloudIdCache.NodeInfo nodeInfo = idCache.get(file.getPath()); - if (nodeInfo != null) { - fileId = nodeInfo.getId(); - } - } - - RemoteFile remoteFile = null; - if (fileId == null) { - Optional remoteEntryOptional = findEntry(file.getParent().getId(), file.getName(), false); - if (remoteEntryOptional.isPresent()) { - remoteFile = remoteEntryOptional.get().asFile(); - fileId = remoteFile.fileId(); - } else { - throw new NoSuchCloudFileException(file.getName()); - } - } - Optional cacheKey = Optional.empty(); Optional cacheFile = Optional.empty(); + RemoteFile remoteFile; + if (sharedPreferencesHandler.useLruCache() && createLruCache(sharedPreferencesHandler.lruCacheSize())) { - if (remoteFile == null) { - try { - remoteFile = client().loadFile(fileId).execute().asFile(); - cacheKey = Optional.of(remoteFile.fileId() + remoteFile.hash()); - } catch(ApiError ex) { - handleApiError(ex); - } + try { + remoteFile = client().loadFile(file.getPath()).execute().asFile(); + cacheKey = Optional.of(remoteFile.fileId() + remoteFile.hash()); + } catch(ApiError ex) { + handleApiError(ex); } File cachedFile = diskLruCache.get(cacheKey.get()); @@ -376,23 +266,22 @@ class PCloudImpl { retrieveFromLruCache(cacheFile.get(), data); } catch (IOException e) { Timber.tag("PCloudImpl").w(e, "Error while retrieving content from Cache, get from web request"); - writeToData(file, fileId, data, encryptedTmpFile, cacheKey, progressAware); + writeToData(file, data, encryptedTmpFile, cacheKey, progressAware); } } else { - writeToData(file, fileId, data, encryptedTmpFile, cacheKey, progressAware); + writeToData(file, data, encryptedTmpFile, cacheKey, progressAware); } progressAware.onProgress(Progress.completed(DownloadState.download(file))); } private void writeToData(final PCloudFile file, // - final long fileId, // final OutputStream data, // final Optional encryptedTmpFile, // final Optional cacheKey, // final ProgressAware progressAware) throws IOException, BackendException { try { - FileLink fileLink = client().createFileLink(fileId, DownloadOptions.DEFAULT).execute(); + FileLink fileLink = client().createFileLink(file.getPath(), DownloadOptions.DEFAULT).execute(); ProgressListener listener = (done, total) -> progressAware.onProgress( // progress(DownloadState.download(file)) // @@ -426,12 +315,11 @@ class PCloudImpl { try { if (node instanceof PCloudFolder) { client() // - .deleteFolder(node.getId(), true).execute(); + .deleteFolder(node.getPath(), true).execute(); } else { client() // - .deleteFile(node.getId()).execute(); + .deleteFile(node.getPath()).execute(); } - idCache.remove(node); } catch(ApiError ex) { handleApiError(ex); } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java index e58b6775..e460ae2c 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java @@ -4,8 +4,6 @@ import org.cryptomator.domain.CloudNode; interface PCloudNode extends CloudNode { - Long getId(); - @Override PCloudFolder getParent(); diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java index 039add0a..55e72da9 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNodeFactory.java @@ -9,27 +9,27 @@ import org.cryptomator.util.Optional; class PCloudNodeFactory { public static PCloudFile file(PCloudFolder parent, RemoteFile file) { - return new PCloudFile(parent, file.name(), getNodePath(parent, file.name()), file.fileId(), Optional.ofNullable(file.size()), Optional.ofNullable(file.lastModified())); + return new PCloudFile(parent, file.name(), getNodePath(parent, file.name()), Optional.ofNullable(file.size()), Optional.ofNullable(file.lastModified())); } public static PCloudFile file(PCloudFolder parent, String name, Optional size) { - return new PCloudFile(parent, name, getNodePath(parent, name), null, size, Optional.empty()); + return new PCloudFile(parent, name, getNodePath(parent, name), size, Optional.empty()); } - public static PCloudFile file(PCloudFolder folder, String name, Optional size, String path, Long fileId) { - return new PCloudFile(folder, name, path, fileId, size, Optional.empty()); + public static PCloudFile file(PCloudFolder folder, String name, Optional size, String path) { + return new PCloudFile(folder, name, path, size, Optional.empty()); } public static PCloudFolder folder(PCloudFolder parent, RemoteFolder folder) { - return new PCloudFolder(parent, folder.name(), getNodePath(parent, folder.name()), folder.folderId()); + return new PCloudFolder(parent, folder.name(), getNodePath(parent, folder.name())); } public static PCloudFolder folder(PCloudFolder parent, String name) { - return new PCloudFolder(parent, name, getNodePath(parent, name), null); + return new PCloudFolder(parent, name, getNodePath(parent, name)); } - public static PCloudFolder folder(PCloudFolder parent, String name, String path, Long folderId) { - return new PCloudFolder(parent, name, path, folderId); + public static PCloudFolder folder(PCloudFolder parent, String name, String path) { + return new PCloudFolder(parent, name, path); } public static String getNodePath(PCloudFolder parent, String name) { diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java index 60ac96f0..fd819a92 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/RootPCloudFolder.java @@ -1,7 +1,5 @@ package org.cryptomator.data.cloud.pcloud; -import com.pcloud.sdk.RemoteFolder; - import org.cryptomator.domain.Cloud; import org.cryptomator.domain.PCloud; @@ -10,7 +8,7 @@ class RootPCloudFolder extends PCloudFolder { private final PCloud cloud; public RootPCloudFolder(PCloud cloud) { - super(null, "", "", (long) RemoteFolder.ROOT_FOLDER_ID); + super(null, "", ""); this.cloud = cloud; } From fe07f376b560fbb10159267f814fb872b6814699 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 25 Mar 2021 15:34:37 +0100 Subject: [PATCH 80/93] fix: use proper icons --- .../org/cryptomator/presentation/model/CloudTypeModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt index 081c739c..5b7a86c6 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/model/CloudTypeModel.kt @@ -19,9 +19,9 @@ enum class CloudTypeModel(builder: Builder) { .withVaultImageResource(R.drawable.onedrive_vault) // .withVaultSelectedImageResource(R.drawable.onedrive_vault_selected)), // PCLOUD(Builder("PCLOUD", R.string.cloud_names_pcloud) // - //TODO: change icons for pCloud - .withCloudImageResource(R.drawable.storage_type_local) // - .withCloudImageLargeResource(R.drawable.storage_type_local_large) // + .withCloudImageResource(R.drawable.pcloud) // + .withVaultImageResource(R.drawable.pcloud_vault) // + .withVaultSelectedImageResource(R.drawable.pcloud_vault_selected) // .withMultiInstances()), // WEBDAV(Builder("WEBDAV", R.string.cloud_names_webdav) // .withCloudImageResource(R.drawable.webdav) // From e35f1d60870247b5667aa60834fd30dfbb474efa Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 25 Mar 2021 17:17:24 +0100 Subject: [PATCH 81/93] fix: list for root folder --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 3a00a36f..8861ba47 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -134,7 +134,8 @@ class PCloudImpl { List result = new ArrayList<>(); try { - RemoteFolder listFolderResult = client().listFolder(folder.getPath()).execute(); + String path = folder.getPath().isEmpty() ? "/" : folder.getPath(); + RemoteFolder listFolderResult = client().listFolder(path).execute(); List entryMetadata = listFolderResult.children(); for (RemoteEntry metadata : entryMetadata) { result.add(PCloudNodeFactory.from(folder, metadata)); From 5695a5c0ede6463ebbf2f16e2fa483c31531573b Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 25 Mar 2021 17:31:40 +0100 Subject: [PATCH 82/93] Revert "fix: list for root folder" This reverts commit a2215694b1a25312dfeaced866561ec1447f09a9. --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 8861ba47..3a00a36f 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -134,8 +134,7 @@ class PCloudImpl { List result = new ArrayList<>(); try { - String path = folder.getPath().isEmpty() ? "/" : folder.getPath(); - RemoteFolder listFolderResult = client().listFolder(path).execute(); + RemoteFolder listFolderResult = client().listFolder(folder.getPath()).execute(); List entryMetadata = listFolderResult.children(); for (RemoteEntry metadata : entryMetadata) { result.add(PCloudNodeFactory.from(folder, metadata)); From fe87ae3126184580663fcfc8096b07349b6ed40f Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 25 Mar 2021 19:44:58 +0100 Subject: [PATCH 83/93] fix: only take username into account in configurationMatches() --- domain/src/main/java/org/cryptomator/domain/PCloud.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/src/main/java/org/cryptomator/domain/PCloud.java b/domain/src/main/java/org/cryptomator/domain/PCloud.java index d81a2cdc..95164c65 100644 --- a/domain/src/main/java/org/cryptomator/domain/PCloud.java +++ b/domain/src/main/java/org/cryptomator/domain/PCloud.java @@ -56,7 +56,7 @@ public class PCloud implements Cloud { } private boolean configurationMatches(PCloud cloud) { - return url.equals(cloud.url) && username.equals(cloud.username); + return username.equals(cloud.username); } From e1bc82b89abcecfa25a8fe3ee9abedcd060609af Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 25 Mar 2021 20:29:51 +0100 Subject: [PATCH 84/93] fix: throw NoSuchCloudFileException if API Error Code is 2002 --- .../java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java index 5c594ae6..bd9a94da 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java @@ -71,7 +71,8 @@ public class PCloudApiError { } public static boolean isNoSuchCloudFileException(int errorCode) { - return errorCode == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue() + return errorCode == PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue() + || errorCode == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue() || errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue() || errorCode == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue(); } From 6217c424404231e238d131dcf914d89e891e115f Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 25 Mar 2021 20:33:46 +0100 Subject: [PATCH 85/93] chore: add TODO regarding filename encoding --- .../main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 3a00a36f..b92e21bc 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -224,6 +224,7 @@ class PCloudImpl { } }; + //TODO: remove filename encoding as soon as it is fixed API wise: https://github.com/pCloud/pcloud-sdk-java/issues/13 String filename = file.getName(); String encodedFilename = URLEncoder.encode(filename, UTF_8); From c57a59c29b8988c57921a658c0711ff60b62644a Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Thu, 25 Mar 2021 20:49:35 +0100 Subject: [PATCH 86/93] fix: remove filename encoding --- .../org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index b92e21bc..94fab8a8 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -224,17 +224,10 @@ class PCloudImpl { } }; - //TODO: remove filename encoding as soon as it is fixed API wise: https://github.com/pCloud/pcloud-sdk-java/issues/13 - String filename = file.getName(); - String encodedFilename = URLEncoder.encode(filename, UTF_8); - try { RemoteFile newFile = client() // - .createFile(file.getParent().getPath(), encodedFilename, pCloudDataSource, new Date(), listener, uploadOptions) // + .createFile(file.getParent().getPath(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // .execute(); - if (!filename.equals(encodedFilename)) { - return client().renameFile(newFile.fileId(), filename).execute(); - } return newFile; } catch (ApiError ex) { handleApiError(ex); From aaf7f4bfb53bd7cab6eb1b7098e6d037ae3f3523 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 26 Mar 2021 13:09:31 +0100 Subject: [PATCH 87/93] fix: remove file encoding left overs --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 94fab8a8..918faab5 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -38,7 +38,6 @@ import org.cryptomator.util.file.LruFileCacheUtil; import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; @@ -66,8 +65,6 @@ class PCloudImpl { private final SharedPreferencesHandler sharedPreferencesHandler; private DiskLruCache diskLruCache; - private final String UTF_8 = "UTF-8"; - PCloudImpl(Context context, PCloud cloud) { if (cloud.accessToken() == null) { throw new NoAuthenticationProvidedException(cloud); From d3609ac6917f80e2a990bfedd5795fee4765c055 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 26 Mar 2021 13:11:10 +0100 Subject: [PATCH 88/93] fix: directly return newFile --- .../java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 918faab5..bf2844f0 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -222,10 +222,9 @@ class PCloudImpl { }; try { - RemoteFile newFile = client() // + return client() // .createFile(file.getParent().getPath(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // .execute(); - return newFile; } catch (ApiError ex) { handleApiError(ex); throw new FatalBackendException(ex); From 728d5262ab00d34258ad7ed199c2182c8f3e232c Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 26 Mar 2021 13:35:56 +0100 Subject: [PATCH 89/93] fix: handleApiError() with name --- .../data/cloud/pcloud/PCloudImpl.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index bf2844f0..873e82e3 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -122,7 +122,7 @@ class PCloudImpl { ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue()); ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue()); ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()); - handleApiError(ex, ignoredErrorCodes); + handleApiError(ex, ignoredErrorCodes, node.getName()); return false; } } @@ -138,7 +138,7 @@ class PCloudImpl { } return result; } catch(ApiError ex) { - handleApiError(ex); + handleApiError(ex, folder.getName()); throw new FatalBackendException(ex); } } @@ -157,7 +157,7 @@ class PCloudImpl { .execute(); return PCloudNodeFactory.folder(folder.getParent(), createdFolder); } catch (ApiError ex) { - handleApiError(ex); + handleApiError(ex, folder.getName()); throw new FatalBackendException(ex); } } @@ -174,7 +174,7 @@ class PCloudImpl { return PCloudNodeFactory.from(target.getParent(), client().moveFile(source.getPath(), target.getPath()).execute()); } } catch(ApiError ex) { - handleApiError(ex); + handleApiError(ex, source.getName() + " / " + target.getName()); throw new FatalBackendException(ex); } } @@ -226,7 +226,7 @@ class PCloudImpl { .createFile(file.getParent().getPath(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // .execute(); } catch (ApiError ex) { - handleApiError(ex); + handleApiError(ex, file.getName()); throw new FatalBackendException(ex); } } @@ -244,7 +244,7 @@ class PCloudImpl { remoteFile = client().loadFile(file.getPath()).execute().asFile(); cacheKey = Optional.of(remoteFile.fileId() + remoteFile.hash()); } catch(ApiError ex) { - handleApiError(ex); + handleApiError(ex, file.getName()); } File cachedFile = diskLruCache.get(cacheKey.get()); @@ -288,7 +288,7 @@ class PCloudImpl { client().download(fileLink, sink, listener).execute(); } catch(ApiError ex) { - handleApiError(ex); + handleApiError(ex, file.getName()); } if (sharedPreferencesHandler.useLruCache() && encryptedTmpFile.isPresent() && cacheKey.isPresent()) { @@ -311,7 +311,7 @@ class PCloudImpl { .deleteFile(node.getPath()).execute(); } } catch(ApiError ex) { - handleApiError(ex); + handleApiError(ex, node.getName()); } } @@ -341,11 +341,11 @@ class PCloudImpl { } private void handleApiError(ApiError ex) throws BackendException { - handleApiError(ex, null); + handleApiError(ex, null, null); } - private void handleApiError(ApiError ex, Set errorCodes) throws BackendException { - handleApiError(ex, errorCodes, null); + private void handleApiError(ApiError ex, String name) throws BackendException { + handleApiError(ex, null, name); } private void handleApiError(ApiError ex, Set errorCodes, String name) throws BackendException { From d227c5c0fd205e7366593cbff3964ff472529ef5 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Fri, 26 Mar 2021 14:16:17 +0100 Subject: [PATCH 90/93] fix: use static sets, user proper name --- .../data/cloud/pcloud/PCloudApiError.java | 23 +++++++++++++++++++ .../data/cloud/pcloud/PCloudImpl.java | 15 ++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java index bd9a94da..37ceb3bd 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java @@ -1,5 +1,8 @@ package org.cryptomator.data.cloud.pcloud; +import java.util.Arrays; +import java.util.HashSet; + public class PCloudApiError { public enum PCloudApiErrorCodes { @@ -51,6 +54,26 @@ public class PCloudApiError { } } + public static final HashSet ignoreExistsSet = new HashSet<>( + Arrays.asList( + PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), + PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), + PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), + PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue(), + PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue() + ) + ); + + public static final HashSet ignoreMoveSet = new HashSet<>( + Arrays.asList( + PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue(), + PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), + PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), + PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), + PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() + ) + ); + public static boolean isCloudNodeAlreadyExistsException(int errorCode) { return errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue(); } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 873e82e3..48984e51 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -117,12 +117,7 @@ class PCloudImpl { } return true; } catch (ApiError ex) { - Set ignoredErrorCodes = new HashSet<>(); - ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue()); - ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue()); - ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue()); - ignoredErrorCodes.add(PCloudApiError.PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()); - handleApiError(ex, ignoredErrorCodes, node.getName()); + handleApiError(ex, PCloudApiError.ignoreExistsSet, node.getName()); return false; } } @@ -174,7 +169,13 @@ class PCloudImpl { return PCloudNodeFactory.from(target.getParent(), client().moveFile(source.getPath(), target.getPath()).execute()); } } catch(ApiError ex) { - handleApiError(ex, source.getName() + " / " + target.getName()); + if (PCloudApiError.isCloudNodeAlreadyExistsException(ex.errorCode())) { + throw new CloudNodeAlreadyExistsException(target.getName()); + } else if (PCloudApiError.isNoSuchCloudFileException(ex.errorCode())) { + throw new NoSuchCloudFileException(source.getName()); + } else { + handleApiError(ex, PCloudApiError.ignoreMoveSet, null); + } throw new FatalBackendException(ex); } } From 483a831618a92efc672017a6226c48830306b353 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 26 Mar 2021 16:37:49 +0100 Subject: [PATCH 91/93] Re-authenticate if credential is expired --- presentation/src/main/res/values/strings.xml | 1 + .../presenter/AuthenticateCloudPresenter.kt | 102 ++++++++++++++++-- 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 67ef7c01..6ef89ee7 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ An error occurred Authentication failed + Authentication failed, please login using %1$s No network connection Wrong password A file or folder already exists. diff --git a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt index a1045799..8b228d10 100644 --- a/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt +++ b/presentation/src/notFoss/java/org/cryptomator/presentation/presenter/AuthenticateCloudPresenter.kt @@ -3,9 +3,15 @@ package org.cryptomator.presentation.presenter import android.Manifest import android.accounts.AccountManager import android.content.ActivityNotFoundException +import android.content.Intent +import android.widget.Toast import com.dropbox.core.android.Auth import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential import com.google.api.services.drive.DriveScopes +import com.pcloud.sdk.AuthorizationActivity +import com.pcloud.sdk.AuthorizationData +import com.pcloud.sdk.AuthorizationRequest +import com.pcloud.sdk.AuthorizationResult import org.cryptomator.data.cloud.onedrive.OnedriveClientFactory import org.cryptomator.data.cloud.onedrive.graph.ClientException import org.cryptomator.data.cloud.onedrive.graph.ICallback @@ -15,6 +21,7 @@ import org.cryptomator.domain.CloudType import org.cryptomator.domain.DropboxCloud import org.cryptomator.domain.GoogleDriveCloud import org.cryptomator.domain.OnedriveCloud +import org.cryptomator.domain.PCloud import org.cryptomator.domain.WebDavCloud import org.cryptomator.domain.di.PerView import org.cryptomator.domain.exception.FatalBackendException @@ -25,6 +32,7 @@ import org.cryptomator.domain.exception.authentication.WebDavNotSupportedExcepti import org.cryptomator.domain.exception.authentication.WebDavServerNotFoundException import org.cryptomator.domain.exception.authentication.WrongCredentialsException import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase +import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase import org.cryptomator.generator.Callback import org.cryptomator.presentation.BuildConfig @@ -34,7 +42,6 @@ import org.cryptomator.presentation.exception.PermissionNotGrantedException import org.cryptomator.presentation.intent.AuthenticateCloudIntent import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel -import org.cryptomator.presentation.model.PCloudModel import org.cryptomator.presentation.model.ProgressModel import org.cryptomator.presentation.model.ProgressStateModel import org.cryptomator.presentation.model.WebDavCloudModel @@ -58,6 +65,7 @@ class AuthenticateCloudPresenter @Inject constructor( // exceptionHandlers: ExceptionHandlers, // private val cloudModelMapper: CloudModelMapper, // private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, // + private val getCloudsUseCase: GetCloudsUseCase, // private val getUsernameUseCase: GetUsernameUseCase, // private val addExistingVaultWorkflow: AddExistingVaultWorkflow, // private val createNewVaultWorkflow: CreateNewVaultWorkflow) : Presenter(exceptionHandlers) { @@ -286,22 +294,98 @@ class AuthenticateCloudPresenter @Inject constructor( // private inner class PCloudAuthStrategy : AuthStrategy { + private var authenticationStarted = false + override fun supports(cloud: CloudModel): Boolean { return cloud.cloudType() == CloudTypeModel.PCLOUD } override fun resumed(intent: AuthenticateCloudIntent) { - handlePCloudAuthenticationExceptionIfRequired(intent.cloud() as PCloudModel, intent.error()) - } - - private fun handlePCloudAuthenticationExceptionIfRequired(cloud: PCloudModel, e: AuthenticationException) { - Timber.tag("AuthicateCloudPrester").e(e) when { - ExceptionUtil.contains(e, WrongCredentialsException::class.java) -> { - failAuthentication(cloud.name()) + ExceptionUtil.contains(intent.error(), WrongCredentialsException::class.java) -> { + if (!authenticationStarted) { + startAuthentication() + Toast.makeText( + context(), + String.format(getString(R.string.error_authentication_failed_re_authenticate), intent.cloud().username()), + Toast.LENGTH_LONG).show() + } + } + else -> { + Timber.tag("AuthicateCloudPrester").e(intent.error()) + failAuthentication(intent.cloud().name()) } } } + + private fun startAuthentication() { + authenticationStarted = true + val authIntent: Intent = AuthorizationActivity.createIntent( + context(), + AuthorizationRequest.create() + .setType(AuthorizationRequest.Type.TOKEN) + .setClientId(BuildConfig.PCLOUD_CLIENT_ID) + .setForceAccessApproval(true) + .addPermission("manageshares") + .build()) + requestActivityResult(ActivityResultCallbacks.pCloudReAuthenticationFinished(), // + authIntent) + } + } + + @Callback + fun pCloudReAuthenticationFinished(activityResult: ActivityResult) { + val authData: AuthorizationData = AuthorizationActivity.getResult(activityResult.intent()) + val result: AuthorizationResult = authData.result + + when (result) { + AuthorizationResult.ACCESS_GRANTED -> { + val accessToken: String = CredentialCryptor // + .getInstance(context()) // + .encrypt(authData.token) + val pCloudSkeleton: PCloud = PCloud.aPCloud() // + .withAccessToken(accessToken) + .withUrl(authData.apiHost) + .build(); + getUsernameUseCase // + .withCloud(pCloudSkeleton) // + .run(object : DefaultResultHandler() { + override fun onSuccess(username: String?) { + prepareForSavingPCloud(PCloud.aCopyOf(pCloudSkeleton).withUsername(username).build()) + } + }) + } + AuthorizationResult.ACCESS_DENIED -> { + Timber.tag("CloudConnListPresenter").e("Account access denied") + view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud))) + } + AuthorizationResult.AUTH_ERROR -> { + Timber.tag("CloudConnListPresenter").e("""Account access grant error: ${authData.errorMessage}""".trimIndent()) + view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud))) + } + AuthorizationResult.CANCELLED -> { + Timber.tag("CloudConnListPresenter").i("Account access grant cancelled") + view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(R.string.cloud_names_pcloud))) + } + } + } + + fun prepareForSavingPCloud(cloud: PCloud) { + getCloudsUseCase // + .withCloudType(cloud.type()) // + .run(object : DefaultResultHandler>() { + override fun onSuccess(clouds: List) { + clouds.firstOrNull { + (it as PCloud).username() == cloud.username() + }?.let { + it as PCloud + succeedAuthenticationWith(PCloud.aCopyOf(it) // + .withUrl(cloud.url()) + .withAccessToken(cloud.accessToken()) + .build()) + } ?: succeedAuthenticationWith(cloud) + } + }) } private inner class WebDAVAuthStrategy : AuthStrategy { @@ -425,6 +509,6 @@ class AuthenticateCloudPresenter @Inject constructor( // } init { - unsubscribeOnDestroy(addOrChangeCloudConnectionUseCase, getUsernameUseCase) + unsubscribeOnDestroy(addOrChangeCloudConnectionUseCase, getCloudsUseCase, getUsernameUseCase) } } From 9843e77c3f5e46142d27235dff165df49a2860f0 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Fri, 26 Mar 2021 16:49:58 +0100 Subject: [PATCH 92/93] Reformat code to apply to project style guide --- .../data/cloud/pcloud/PCloudApiError.java | 184 +++++++++--------- .../cloud/pcloud/PCloudContentRepository.java | 12 +- .../data/cloud/pcloud/PCloudImpl.java | 32 ++- .../org/cryptomator/data/db/Upgrade2To3.kt | 2 +- .../data/db/entities/VaultEntity.java | 4 +- .../data/db/mappers/CloudEntityMapper.java | 2 +- .../presenter/CloudConnectionListPresenter.kt | 4 +- .../ui/activity/ImagePreviewActivity.kt | 2 +- .../ui/adapter/CloudConnectionListAdapter.kt | 4 +- 9 files changed, 122 insertions(+), 124 deletions(-) diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java index 37ceb3bd..d502576d 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudApiError.java @@ -5,42 +5,97 @@ import java.util.HashSet; public class PCloudApiError { + public static final HashSet ignoreExistsSet = new HashSet<>( // + Arrays.asList( // + PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), // + PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), // + PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), // + PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue(), // + PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue() // + )); + public static final HashSet ignoreMoveSet = new HashSet<>( // + Arrays.asList( // + PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue(), // + PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), // + PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), // + PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), // + PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() // + ) // + ); + + public static boolean isCloudNodeAlreadyExistsException(int errorCode) { + return errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue(); + } + + public static boolean isFatalBackendException(int errorCode) { + return errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() // + || errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() // + || errorCode == PCloudApiErrorCodes.UPLOAD_NOT_FOUND.getValue() // + || errorCode == PCloudApiErrorCodes.TRANSFER_NOT_FOUND.getValue(); + } + + public static boolean isForbiddenException(int errorCode) { + return errorCode == PCloudApiErrorCodes.ACCESS_DENIED.getValue(); + } + + public static boolean isNetworkConnectionException(int errorCode) { + return errorCode == PCloudApiErrorCodes.CONNECTION_BROKE.getValue(); + } + + public static boolean isNoSuchCloudFileException(int errorCode) { + return errorCode == PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue() // + || errorCode == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue() // + || errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue() // + || errorCode == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue(); + } + + public static boolean isWrongCredentialsException(int errorCode) { + return errorCode == PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue() // + || errorCode == PCloudApiErrorCodes.ACCESS_TOKEN_REVOKED.getValue(); + } + + public static boolean isUnauthorizedException(int errorCode) { + return errorCode == PCloudApiErrorCodes.LOGIN_FAILED.getValue() // + || errorCode == PCloudApiErrorCodes.LOGIN_REQUIRED.getValue() // + || errorCode == PCloudApiErrorCodes.TOO_MANY_LOGIN_TRIES_FROM_IP.getValue(); + } + public enum PCloudApiErrorCodes { - LOGIN_REQUIRED(1000), - NO_FULL_PATH_OR_NAME_FOLDER_ID_PROVIDED(1001), - NO_FULL_PATH_OR_FOLDER_ID_PROVIDED(1002), - NO_FILE_ID_OR_PATH_PROVIDED(1004), - INVALID_DATE_TIME_FORMAT(1013), - NO_DESTINATION_PROVIDED(1016), - INVALID_FOLDER_ID(1017), - INVALID_DESTINATION(1037), - PROVIDE_URL(1040), - UPLOAD_NOT_FOUND(1900), - TRANSFER_NOT_FOUND(1902), - LOGIN_FAILED(2000), - INVALID_FILE_OR_FOLDER_NAME(2001), - COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST(2002), - ACCESS_DENIED(2003), - FILE_OR_FOLDER_ALREADY_EXISTS(2004), - DIRECTORY_DOES_NOT_EXIST(2005), - FOLDER_NOT_EMPTY(2006), - CANNOT_DELETE_ROOT_FOLDER(2007), - USER_OVER_QUOTA(2008), - FILE_NOT_FOUND(2009), - INVALID_PATH(2010), - SHARED_FOLDER_IN_SHARED_FOLDER(2023), - ACTIVE_SHARES_OR_SHAREREQUESTS_PRESENT(2028), - CONNECTION_BROKE(2041), - CANNOT_RENAME_ROOT_FOLDER(2042), - CANNOT_MOVE_FOLDER_INTO_SUBFOLDER_OF_ITSELF(2043), - FILE_OR_FOLDER_NOT_FOUND(2055), - NO_FILE_UPLOAD_DETECTED(2088), - INVALID_ACCESS_TOKEN(2094), - ACCESS_TOKEN_REVOKED(2095), - TRANSFER_OVER_QUOTA(2097), - TARGET_FOLDER_DOES_NOT_EXIST(2208), - TOO_MANY_LOGIN_TRIES_FROM_IP(4000), - INTERNAL_ERROR(5000), + LOGIN_REQUIRED(1000), // + NO_FULL_PATH_OR_NAME_FOLDER_ID_PROVIDED(1001), // + NO_FULL_PATH_OR_FOLDER_ID_PROVIDED(1002), // + NO_FILE_ID_OR_PATH_PROVIDED(1004), // + INVALID_DATE_TIME_FORMAT(1013), // + NO_DESTINATION_PROVIDED(1016), // + INVALID_FOLDER_ID(1017), // + INVALID_DESTINATION(1037), // + PROVIDE_URL(1040), // + UPLOAD_NOT_FOUND(1900), // + TRANSFER_NOT_FOUND(1902), // + LOGIN_FAILED(2000), // + INVALID_FILE_OR_FOLDER_NAME(2001), // + COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST(2002), // + ACCESS_DENIED(2003), // + FILE_OR_FOLDER_ALREADY_EXISTS(2004), // + DIRECTORY_DOES_NOT_EXIST(2005), // + FOLDER_NOT_EMPTY(2006), // + CANNOT_DELETE_ROOT_FOLDER(2007), // + USER_OVER_QUOTA(2008), // + FILE_NOT_FOUND(2009), // + INVALID_PATH(2010), // + SHARED_FOLDER_IN_SHARED_FOLDER(2023), // + ACTIVE_SHARES_OR_SHAREREQUESTS_PRESENT(2028), // + CONNECTION_BROKE(2041), // + CANNOT_RENAME_ROOT_FOLDER(2042), // + CANNOT_MOVE_FOLDER_INTO_SUBFOLDER_OF_ITSELF(2043), // + FILE_OR_FOLDER_NOT_FOUND(2055), // + NO_FILE_UPLOAD_DETECTED(2088), // + INVALID_ACCESS_TOKEN(2094), // + ACCESS_TOKEN_REVOKED(2095), // + TRANSFER_OVER_QUOTA(2097), // + TARGET_FOLDER_DOES_NOT_EXIST(2208), // + TOO_MANY_LOGIN_TRIES_FROM_IP(4000), // + INTERNAL_ERROR(5000), // INTERNAL_UPLOAD_ERROR(5001); private final int value; @@ -54,61 +109,4 @@ public class PCloudApiError { } } - public static final HashSet ignoreExistsSet = new HashSet<>( - Arrays.asList( - PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), - PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), - PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), - PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue(), - PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue() - ) - ); - - public static final HashSet ignoreMoveSet = new HashSet<>( - Arrays.asList( - PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue(), - PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue(), - PCloudApiErrorCodes.FILE_NOT_FOUND.getValue(), - PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue(), - PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() - ) - ); - - public static boolean isCloudNodeAlreadyExistsException(int errorCode) { - return errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_ALREADY_EXISTS.getValue(); - } - - public static boolean isFatalBackendException(int errorCode) { - return errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() - || errorCode == PCloudApiErrorCodes.INTERNAL_UPLOAD_ERROR.getValue() - || errorCode == PCloudApiErrorCodes.UPLOAD_NOT_FOUND.getValue() - || errorCode == PCloudApiErrorCodes.TRANSFER_NOT_FOUND.getValue(); - } - - public static boolean isForbiddenException(int errorCode) { - return errorCode == PCloudApiErrorCodes.ACCESS_DENIED.getValue(); - } - - public static boolean isNetworkConnectionException(int errorCode) { - return errorCode == PCloudApiErrorCodes.CONNECTION_BROKE.getValue(); - } - - public static boolean isNoSuchCloudFileException(int errorCode) { - return errorCode == PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue() - || errorCode == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue() - || errorCode == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue() - || errorCode == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue(); - } - - public static boolean isWrongCredentialsException(int errorCode) { - return errorCode == PCloudApiErrorCodes.INVALID_ACCESS_TOKEN.getValue() - || errorCode == PCloudApiErrorCodes.ACCESS_TOKEN_REVOKED.getValue(); - } - - public static boolean isUnauthorizedException(int errorCode) { - return errorCode == PCloudApiErrorCodes.LOGIN_FAILED.getValue() - || errorCode == PCloudApiErrorCodes.LOGIN_REQUIRED.getValue() - || errorCode == PCloudApiErrorCodes.TOO_MANY_LOGIN_TRIES_FROM_IP.getValue(); - } - -} \ No newline at end of file +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java index 9c93ddcd..20d1d62a 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudContentRepository.java @@ -47,8 +47,8 @@ class PCloudContentRepository extends InterceptingCloudContentRepository size) throws BackendException { try { return cloud.file(parent, name, size); - } catch(IOException ex) { + } catch (IOException ex) { throw new FatalBackendException(ex); } } @@ -98,7 +98,7 @@ class PCloudContentRepository extends InterceptingCloudContentRepository progressAware, boolean replace, long size) - throws IOException, BackendException { + public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) throws IOException, BackendException { if (!replace && exists(file)) { throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false"); } @@ -202,11 +200,11 @@ class PCloudImpl { private RemoteFile uploadFile(final PCloudFile file, DataSource data, final ProgressAware progressAware, UploadOptions uploadOptions, final long size) // throws IOException, BackendException { - ProgressListener listener = (done, total) -> progressAware.onProgress( // - progress(UploadState.upload(file)) // - .between(0) // - .and(size) // - .withValue(done)); + ProgressListener listener = (done, total) -> progressAware.onProgress( // + progress(UploadState.upload(file)) // + .between(0) // + .and(size) // + .withValue(done)); com.pcloud.sdk.DataSource pCloudDataSource = new com.pcloud.sdk.DataSource() { @Override @@ -244,7 +242,7 @@ class PCloudImpl { try { remoteFile = client().loadFile(file.getPath()).execute().asFile(); cacheKey = Optional.of(remoteFile.fileId() + remoteFile.hash()); - } catch(ApiError ex) { + } catch (ApiError ex) { handleApiError(ex, file.getName()); } @@ -288,7 +286,7 @@ class PCloudImpl { }; client().download(fileLink, sink, listener).execute(); - } catch(ApiError ex) { + } catch (ApiError ex) { handleApiError(ex, file.getName()); } @@ -311,7 +309,7 @@ class PCloudImpl { client() // .deleteFile(node.getPath()).execute(); } - } catch(ApiError ex) { + } catch (ApiError ex) { handleApiError(ex, node.getName()); } } @@ -322,7 +320,7 @@ class PCloudImpl { .getUserInfo() // .execute(); return currentAccount.email(); - } catch(ApiError ex) { + } catch (ApiError ex) { handleApiError(ex); throw new FatalBackendException(ex); } @@ -350,11 +348,11 @@ class PCloudImpl { } private void handleApiError(ApiError ex, Set errorCodes, String name) throws BackendException { - if (errorCodes == null || !errorCodes.contains(ex.errorCode())) { + if (errorCodes == null || !errorCodes.contains(ex.errorCode())) { int errorCode = ex.errorCode(); if (PCloudApiError.isCloudNodeAlreadyExistsException(errorCode)) { throw new CloudNodeAlreadyExistsException(name); - } else if (PCloudApiError.isForbiddenException(errorCode)){ + } else if (PCloudApiError.isForbiddenException(errorCode)) { throw new ForbiddenException(); } else if (PCloudApiError.isNetworkConnectionException(errorCode)) { throw new NetworkConnectionException(ex); diff --git a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt index 183f7333..e87528eb 100644 --- a/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt +++ b/data/src/main/java/org/cryptomator/data/db/Upgrade2To3.kt @@ -17,7 +17,7 @@ internal class Upgrade2To3 @Inject constructor(private val context: Context) : D .columns(listOf("ACCESS_TOKEN")) .where("TYPE", Sql.eq("DROPBOX")) .executeOn(db).use { - if(it.moveToFirst()) { + if (it.moveToFirst()) { Sql.update("CLOUD_ENTITY") .set("ACCESS_TOKEN", Sql.toString(encrypt(it.getString(it.getColumnIndex("ACCESS_TOKEN"))))) .where("TYPE", Sql.eq("DROPBOX")); diff --git a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java index b8d683fc..af12e1ef 100644 --- a/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java +++ b/data/src/main/java/org/cryptomator/data/db/entities/VaultEntity.java @@ -182,7 +182,9 @@ public class VaultEntity extends DatabaseEntity { this.position = position; } - /** called by internal mechanisms, do not call yourself. */ + /** + * called by internal mechanisms, do not call yourself. + */ @Generated(hash = 674742652) public void __setDaoSession(DaoSession daoSession) { this.daoSession = daoSession; diff --git a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java index 5644d6c6..4b637b25 100644 --- a/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java +++ b/data/src/main/java/org/cryptomator/data/db/mappers/CloudEntityMapper.java @@ -4,10 +4,10 @@ import org.cryptomator.data.db.entities.CloudEntity; import org.cryptomator.domain.Cloud; import org.cryptomator.domain.CloudType; import org.cryptomator.domain.DropboxCloud; -import org.cryptomator.domain.PCloud; import org.cryptomator.domain.GoogleDriveCloud; import org.cryptomator.domain.LocalStorageCloud; import org.cryptomator.domain.OnedriveCloud; +import org.cryptomator.domain.PCloud; import org.cryptomator.domain.WebDavCloud; import javax.inject.Inject; diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt index 9efa046a..e5cab69e 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/CloudConnectionListPresenter.kt @@ -4,7 +4,6 @@ import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri import android.os.Build -import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi import com.pcloud.sdk.AuthorizationActivity @@ -228,7 +227,8 @@ class CloudConnectionListPresenter @Inject constructor( // override fun onSuccess(clouds: List) { clouds.firstOrNull { (it as PCloud).username() == cloud.username() - }?.let { it as PCloud + }?.let { + it as PCloud saveCloud(PCloud.aCopyOf(it) // .withUrl(cloud.url()) .withAccessToken(cloud.accessToken()) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt index f44a24f8..35d65634 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/ImagePreviewActivity.kt @@ -207,7 +207,7 @@ class ImagePreviewActivity : BaseActivity(), ImagePreviewView, ConfirmDeleteClou presenter.pageIndexes.size.let { when { it == 0 -> { - showMessage(getString(R.string.dialog_no_more_images_to_display )) + showMessage(getString(R.string.dialog_no_more_images_to_display)) finish() } it > index -> updateTitle(index) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt index 40793409..a632a3ac 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/adapter/CloudConnectionListAdapter.kt @@ -74,8 +74,8 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter Date: Fri, 26 Mar 2021 20:59:19 +0100 Subject: [PATCH 93/93] Update to latest version of pcloud-sdk-java --- pcloud-sdk-java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcloud-sdk-java b/pcloud-sdk-java index 0c550f2f..d12c6e6c 160000 --- a/pcloud-sdk-java +++ b/pcloud-sdk-java @@ -1 +1 @@ -Subproject commit 0c550f2fe05336f4e49202b500b784eb4cbe2712 +Subproject commit d12c6e6c4af8d0360812900663d5298ca093377b