mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 18:35:40 +03:00
Merge branch 'develop' into feature/aris/threads_live_timeline
# Conflicts: # matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
This commit is contained in:
commit
f1b11df781
86 changed files with 2511 additions and 1222 deletions
15
.github/workflows/sanity_test.yml
vendored
15
.github/workflows/sanity_test.yml
vendored
|
@ -8,7 +8,7 @@ on:
|
|||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
CI_GRADLE_ARG_PROPERTIES: >
|
||||
-Porg.gradle.jvmargs=-Xmx2g
|
||||
-Porg.gradle.jvmargs=-Xmx4g
|
||||
-Porg.gradle.parallel=false
|
||||
|
||||
jobs:
|
||||
|
@ -18,7 +18,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
api-level: [ 29 ]
|
||||
api-level: [ 28 ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
|
@ -57,9 +57,11 @@ jobs:
|
|||
- name: Run sanity tests on API ${{ matrix.api-level }}
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
api-level: ${{ matrix.api-level }}
|
||||
profile: 24 # Pixel 5
|
||||
arch: x86
|
||||
profile: Nexus 5X
|
||||
force-avd-creation: false
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
|
||||
script: |
|
||||
adb root
|
||||
|
@ -67,10 +69,9 @@ jobs:
|
|||
touch emulator.log
|
||||
chmod 777 emulator.log
|
||||
adb logcat >> emulator.log &
|
||||
./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1
|
||||
- name: Upload Failing Test Report Log
|
||||
./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots
|
||||
- name: Upload Test Report Log
|
||||
uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: sanity-error-results
|
||||
path: |
|
||||
|
|
1
changelog.d/4640.bugfix
Normal file
1
changelog.d/4640.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Right align the notifications badge in the rooms list (and DMs) so that it's always in a consistent place on the screen.
|
1
changelog.d/5204.feature
Normal file
1
changelog.d/5204.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Improve UI of reactions in timeline, including quick add reaction.
|
1
changelog.d/5209.misc
Normal file
1
changelog.d/5209.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Reduce verbosity of debug logging,
|
1
changelog.d/5210.misc
Normal file
1
changelog.d/5210.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Standardise emulator versions of GHA integration tests.
|
|
@ -8,7 +8,7 @@
|
|||
# The setting is particularly useful for tweaking memory settings.
|
||||
|
||||
# Build Time Optimizations
|
||||
org.gradle.jvmargs=-Xmx3g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC
|
||||
org.gradle.jvmargs=-Xmx4g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.vfs.watch=true
|
||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=c9490e938b221daf0094982288e4038deed954a3f12fb54cbf270ddf4e37d879
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
distributionSha256Sum=cd5c2958a107ee7f0722004a12d0f8559b4564c34daad7df06cffd4d12a426d0
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -58,9 +58,9 @@
|
|||
|
||||
<!-- Other colors, which are not in the palette -->
|
||||
<attr name="vctr_unread_room_badge" format="color" />
|
||||
<color name="vctr_unread_room_badge_light">#FF61708B</color>
|
||||
<color name="vctr_unread_room_badge_dark">#FF61708B</color>
|
||||
<color name="vctr_unread_room_badge_black">#FF61708B</color>
|
||||
<color name="vctr_unread_room_badge_light">@color/palette_gray_200</color>
|
||||
<color name="vctr_unread_room_badge_dark">@color/palette_gray_250</color>
|
||||
<color name="vctr_unread_room_badge_black">@color/palette_gray_250</color>
|
||||
|
||||
<attr name="vctr_fab_label_bg" format="color" />
|
||||
<color name="vctr_fab_label_bg_light">@android:color/white</color>
|
||||
|
|
|
@ -23,4 +23,15 @@
|
|||
<item name="android:backgroundTint">?vctr_content_quinary</item>
|
||||
</style>
|
||||
|
||||
<style name="TimelineReactionView">
|
||||
<item name="android:paddingStart">6dp</item>
|
||||
<item name="android:paddingEnd">6dp</item>
|
||||
<item name="android:paddingTop">1dp</item>
|
||||
<item name="android:paddingBottom">1dp</item>
|
||||
<item name="android:minHeight">28dp</item>
|
||||
<item name="android:minWidth">40dp</item>
|
||||
<item name="android:gravity">center</item>
|
||||
</style>
|
||||
|
||||
|
||||
</resources>
|
|
@ -169,7 +169,7 @@ dependencies {
|
|||
implementation libs.apache.commonsImaging
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.42'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.43'
|
||||
|
||||
testImplementation libs.tests.junit
|
||||
testImplementation 'org.robolectric:robolectric:4.7.3'
|
||||
|
|
|
@ -46,7 +46,9 @@ internal abstract class AuthModule {
|
|||
@JvmStatic
|
||||
@Provides
|
||||
@AuthDatabase
|
||||
fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||
fun providesRealmConfiguration(context: Context,
|
||||
realmKeysUtils: RealmKeysUtils,
|
||||
authRealmMigration: AuthRealmMigration): RealmConfiguration {
|
||||
val old = File(context.filesDir, "matrix-sdk-auth")
|
||||
if (old.exists()) {
|
||||
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
|
||||
|
@ -58,8 +60,8 @@ internal abstract class AuthModule {
|
|||
}
|
||||
.name("matrix-sdk-auth.realm")
|
||||
.modules(AuthRealmModule())
|
||||
.schemaVersion(AuthRealmMigration.SCHEMA_VERSION)
|
||||
.migration(AuthRealmMigration)
|
||||
.schemaVersion(authRealmMigration.schemaVersion)
|
||||
.migration(authRealmMigration)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,102 +16,31 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.auth.db
|
||||
|
||||
import android.net.Uri
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.sessionId
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo001
|
||||
import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo002
|
||||
import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo003
|
||||
import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo004
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object AuthRealmMigration : RealmMigration {
|
||||
internal class AuthRealmMigration @Inject constructor() : RealmMigration {
|
||||
/**
|
||||
* Forces all AuthRealmMigration instances to be equal
|
||||
* Avoids Realm throwing when multiple instances of the migration are set
|
||||
*/
|
||||
override fun equals(other: Any?) = other is AuthRealmMigration
|
||||
override fun hashCode() = 4000
|
||||
|
||||
// Current schema version
|
||||
const val SCHEMA_VERSION = 4L
|
||||
val schemaVersion = 4L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
if (oldVersion <= 1) migrateTo2(realm)
|
||||
if (oldVersion <= 2) migrateTo3(realm)
|
||||
if (oldVersion <= 3) migrateTo4(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
Timber.d("Step 0 -> 1")
|
||||
Timber.d("Create PendingSessionEntity")
|
||||
|
||||
realm.schema.create("PendingSessionEntity")
|
||||
.addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
|
||||
.setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
|
||||
.addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java)
|
||||
.setRequired(PendingSessionEntityFields.CLIENT_SECRET, true)
|
||||
.addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java)
|
||||
.setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true)
|
||||
.addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java)
|
||||
.addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java)
|
||||
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
|
||||
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
|
||||
}
|
||||
|
||||
private fun migrateTo2(realm: DynamicRealm) {
|
||||
Timber.d("Step 1 -> 2")
|
||||
Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
|
||||
|
||||
realm.schema.get("SessionParamsEntity")
|
||||
?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
|
||||
?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
|
||||
}
|
||||
|
||||
private fun migrateTo3(realm: DynamicRealm) {
|
||||
Timber.d("Step 2 -> 3")
|
||||
Timber.d("Update SessionParamsEntity primary key, to allow several sessions with the same userId")
|
||||
|
||||
realm.schema.get("SessionParamsEntity")
|
||||
?.removePrimaryKey()
|
||||
?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java)
|
||||
?.setRequired(SessionParamsEntityFields.SESSION_ID, true)
|
||||
?.transform {
|
||||
val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON)
|
||||
|
||||
val credentials = MoshiProvider.providesMoshi()
|
||||
.adapter(Credentials::class.java)
|
||||
.fromJson(credentialsJson)
|
||||
|
||||
it.set(SessionParamsEntityFields.SESSION_ID, credentials!!.sessionId())
|
||||
}
|
||||
?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
|
||||
}
|
||||
|
||||
private fun migrateTo4(realm: DynamicRealm) {
|
||||
Timber.d("Step 3 -> 4")
|
||||
Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value")
|
||||
|
||||
val adapter = MoshiProvider.providesMoshi()
|
||||
.adapter(HomeServerConnectionConfig::class.java)
|
||||
|
||||
realm.schema.get("SessionParamsEntity")
|
||||
?.transform {
|
||||
val homeserverConnectionConfigJson = it.getString(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON)
|
||||
|
||||
val homeserverConnectionConfig = adapter
|
||||
.fromJson(homeserverConnectionConfigJson)
|
||||
|
||||
val homeserverUrl = homeserverConnectionConfig?.homeServerUri?.toString()
|
||||
// Special case for matrix.org. Old session may use "https://matrix.org", newer one may use
|
||||
// "https://matrix-client.matrix.org". So fix that here
|
||||
val alteredHomeserverConnectionConfig =
|
||||
if (homeserverUrl == "https://matrix.org" || homeserverUrl == "https://matrix-client.matrix.org") {
|
||||
homeserverConnectionConfig.copy(
|
||||
homeServerUri = Uri.parse("https://matrix.org"),
|
||||
homeServerUriBase = Uri.parse("https://matrix-client.matrix.org")
|
||||
)
|
||||
} else {
|
||||
homeserverConnectionConfig
|
||||
}
|
||||
it.set(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, adapter.toJson(alteredHomeserverConnectionConfig))
|
||||
}
|
||||
if (oldVersion < 1) MigrateAuthTo001(realm).perform()
|
||||
if (oldVersion < 2) MigrateAuthTo002(realm).perform()
|
||||
if (oldVersion < 3) MigrateAuthTo003(realm).perform()
|
||||
if (oldVersion < 4) MigrateAuthTo004(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.auth.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.auth.db.PendingSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateAuthTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Create PendingSessionEntity")
|
||||
|
||||
realm.schema.create("PendingSessionEntity")
|
||||
.addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
|
||||
.setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
|
||||
.addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java)
|
||||
.setRequired(PendingSessionEntityFields.CLIENT_SECRET, true)
|
||||
.addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java)
|
||||
.setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true)
|
||||
.addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java)
|
||||
.addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java)
|
||||
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
|
||||
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.auth.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateAuthTo002(realm: DynamicRealm) : RealmMigrator(realm, 2) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
|
||||
|
||||
realm.schema.get("SessionParamsEntity")
|
||||
?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
|
||||
?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.auth.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.sessionId
|
||||
import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateAuthTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Update SessionParamsEntity primary key, to allow several sessions with the same userId")
|
||||
|
||||
realm.schema.get("SessionParamsEntity")
|
||||
?.removePrimaryKey()
|
||||
?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java)
|
||||
?.setRequired(SessionParamsEntityFields.SESSION_ID, true)
|
||||
?.transform {
|
||||
val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON)
|
||||
|
||||
val credentials = MoshiProvider.providesMoshi()
|
||||
.adapter(Credentials::class.java)
|
||||
.fromJson(credentialsJson)
|
||||
|
||||
it.set(SessionParamsEntityFields.SESSION_ID, credentials!!.sessionId())
|
||||
}
|
||||
?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.auth.db.migration
|
||||
|
||||
import android.net.Uri
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateAuthTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value")
|
||||
|
||||
val adapter = MoshiProvider.providesMoshi()
|
||||
.adapter(HomeServerConnectionConfig::class.java)
|
||||
|
||||
realm.schema.get("SessionParamsEntity")
|
||||
?.transform {
|
||||
val homeserverConnectionConfigJson = it.getString(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON)
|
||||
|
||||
val homeserverConnectionConfig = adapter
|
||||
.fromJson(homeserverConnectionConfigJson)
|
||||
|
||||
val homeserverUrl = homeserverConnectionConfig?.homeServerUri?.toString()
|
||||
// Special case for matrix.org. Old session may use "https://matrix.org", newer one may use
|
||||
// "https://matrix-client.matrix.org". So fix that here
|
||||
val alteredHomeserverConnectionConfig =
|
||||
if (homeserverUrl == "https://matrix.org" || homeserverUrl == "https://matrix-client.matrix.org") {
|
||||
homeserverConnectionConfig.copy(
|
||||
homeServerUri = Uri.parse("https://matrix.org"),
|
||||
homeServerUriBase = Uri.parse("https://matrix-client.matrix.org")
|
||||
)
|
||||
} else {
|
||||
homeserverConnectionConfig
|
||||
}
|
||||
it.set(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, adapter.toJson(alteredHomeserverConnectionConfig))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -112,7 +112,8 @@ internal abstract class CryptoModule {
|
|||
@SessionScope
|
||||
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
|
||||
@UserMd5 userMd5: String,
|
||||
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||
realmKeysUtils: RealmKeysUtils,
|
||||
realmCryptoStoreMigration: RealmCryptoStoreMigration): RealmConfiguration {
|
||||
return RealmConfiguration.Builder()
|
||||
.directory(directory)
|
||||
.apply {
|
||||
|
@ -121,8 +122,8 @@ internal abstract class CryptoModule {
|
|||
.name("crypto_store.realm")
|
||||
.modules(RealmCryptoStoreModule())
|
||||
.allowWritesOnUiThread(true)
|
||||
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
|
||||
.migration(RealmCryptoStoreMigration)
|
||||
.schemaVersion(realmCryptoStoreMigration.schemaVersion)
|
||||
.migration(realmCryptoStoreMigration)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
|
|
@ -16,560 +16,54 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import io.realm.RealmObjectSchema
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.di.SerializeNulls
|
||||
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo001Legacy
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo002Legacy
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo003RiotX
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo004
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo005
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo006
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo007
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo008
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo009
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo010
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo011
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo012
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo013
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo014
|
||||
import timber.log.Timber
|
||||
import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object RealmCryptoStoreMigration : RealmMigration {
|
||||
internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration {
|
||||
/**
|
||||
* Forces all RealmCryptoStoreMigration instances to be equal
|
||||
* Avoids Realm throwing when multiple instances of the migration are set
|
||||
*/
|
||||
override fun equals(other: Any?) = other is RealmCryptoStoreMigration
|
||||
override fun hashCode() = 5000
|
||||
|
||||
// 0, 1, 2: legacy Riot-Android
|
||||
// 3: migrate to RiotX schema
|
||||
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
|
||||
const val CRYPTO_STORE_SCHEMA_VERSION = 14L
|
||||
|
||||
private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
|
||||
if (!hasField(fieldName)) {
|
||||
addField(fieldName, fieldType)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun RealmObjectSchema.removeFieldIfExists(fieldName: String): RealmObjectSchema {
|
||||
if (hasField(fieldName)) {
|
||||
removeField(fieldName)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun RealmObjectSchema.setRequiredIfNotAlready(fieldName: String, isRequired: Boolean): RealmObjectSchema {
|
||||
if (isRequired != isRequired(fieldName)) {
|
||||
setRequired(fieldName, isRequired)
|
||||
}
|
||||
return this
|
||||
}
|
||||
val schemaVersion = 14L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.v("Migrating Realm Crypto from $oldVersion to $newVersion")
|
||||
Timber.d("Migrating Realm Crypto from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) migrateTo1Legacy(realm)
|
||||
if (oldVersion <= 1) migrateTo2Legacy(realm)
|
||||
if (oldVersion <= 2) migrateTo3RiotX(realm)
|
||||
if (oldVersion <= 3) migrateTo4(realm)
|
||||
if (oldVersion <= 4) migrateTo5(realm)
|
||||
if (oldVersion <= 5) migrateTo6(realm)
|
||||
if (oldVersion <= 6) migrateTo7(realm)
|
||||
if (oldVersion <= 7) migrateTo8(realm)
|
||||
if (oldVersion <= 8) migrateTo9(realm)
|
||||
if (oldVersion <= 9) migrateTo10(realm)
|
||||
if (oldVersion <= 10) migrateTo11(realm)
|
||||
if (oldVersion <= 11) migrateTo12(realm)
|
||||
if (oldVersion <= 12) migrateTo13(realm)
|
||||
if (oldVersion <= 13) migrateTo14(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1Legacy(realm: DynamicRealm) {
|
||||
Timber.d("Step 0 -> 1")
|
||||
Timber.d("Add field lastReceivedMessageTs (Long) and set the value to 0")
|
||||
|
||||
realm.schema.get("OlmSessionEntity")
|
||||
?.addField(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Long::class.java)
|
||||
?.transform {
|
||||
it.setLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo2Legacy(realm: DynamicRealm) {
|
||||
Timber.d("Step 1 -> 2")
|
||||
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
|
||||
|
||||
realm.schema.get("IncomingRoomKeyRequestEntity")
|
||||
?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodySessionId", String::class.java)
|
||||
?.transform { dynamicObject ->
|
||||
try {
|
||||
val requestBodyString = dynamicObject.getString("requestBodyString")
|
||||
// It was a map before
|
||||
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
|
||||
|
||||
map?.let {
|
||||
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
|
||||
dynamicObject.setString("requestBodyRoomId", it["room_id"])
|
||||
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
|
||||
dynamicObject.setString("requestBodySessionId", it["session_id"])
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error")
|
||||
}
|
||||
}
|
||||
?.removeFieldIfExists("requestBodyString")
|
||||
|
||||
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
|
||||
|
||||
realm.schema.get("OutgoingRoomKeyRequestEntity")
|
||||
?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodySessionId", String::class.java)
|
||||
?.transform { dynamicObject ->
|
||||
try {
|
||||
val requestBodyString = dynamicObject.getString("requestBodyString")
|
||||
// It was a map before
|
||||
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
|
||||
|
||||
map?.let {
|
||||
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
|
||||
dynamicObject.setString("requestBodyRoomId", it["room_id"])
|
||||
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
|
||||
dynamicObject.setString("requestBodySessionId", it["session_id"])
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error")
|
||||
}
|
||||
}
|
||||
?.removeFieldIfExists("requestBodyString")
|
||||
|
||||
Timber.d("Create KeysBackupDataEntity")
|
||||
|
||||
if (!realm.schema.contains("KeysBackupDataEntity")) {
|
||||
realm.schema.create("KeysBackupDataEntity")
|
||||
.addField(KeysBackupDataEntityFields.PRIMARY_KEY, Integer::class.java)
|
||||
.addPrimaryKey(KeysBackupDataEntityFields.PRIMARY_KEY)
|
||||
.setRequired(KeysBackupDataEntityFields.PRIMARY_KEY, true)
|
||||
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_HASH, String::class.java)
|
||||
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_NUMBER_OF_KEYS, Integer::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo3RiotX(realm: DynamicRealm) {
|
||||
Timber.d("Step 2 -> 3")
|
||||
Timber.d("Migrate to RiotX model")
|
||||
|
||||
realm.schema.get("CryptoRoomEntity")
|
||||
?.addFieldIfNotExists(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, Boolean::class.java)
|
||||
?.setRequiredIfNotAlready(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, false)
|
||||
|
||||
// Convert format of MXDeviceInfo, package has to be the same.
|
||||
realm.schema.get("DeviceInfoEntity")
|
||||
?.transform { obj ->
|
||||
try {
|
||||
val oldSerializedData = obj.getString("deviceInfoData")
|
||||
deserializeFromRealm<LegacyMXDeviceInfo>(oldSerializedData)?.let { legacyMxDeviceInfo ->
|
||||
val newMxDeviceInfo = MXDeviceInfo(
|
||||
deviceId = legacyMxDeviceInfo.deviceId,
|
||||
userId = legacyMxDeviceInfo.userId,
|
||||
algorithms = legacyMxDeviceInfo.algorithms,
|
||||
keys = legacyMxDeviceInfo.keys,
|
||||
signatures = legacyMxDeviceInfo.signatures,
|
||||
unsigned = legacyMxDeviceInfo.unsigned,
|
||||
verified = legacyMxDeviceInfo.mVerified
|
||||
)
|
||||
|
||||
obj.setString("deviceInfoData", serializeForRealm(newMxDeviceInfo))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error")
|
||||
}
|
||||
}
|
||||
|
||||
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper
|
||||
realm.schema.get("OlmInboundGroupSessionEntity")
|
||||
?.transform { obj ->
|
||||
try {
|
||||
val oldSerializedData = obj.getString("olmInboundGroupSessionData")
|
||||
deserializeFromRealm<MXOlmInboundGroupSession2>(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
|
||||
val sessionKey = mxOlmInboundGroupSession2.mSession.sessionIdentifier()
|
||||
val newOlmInboundGroupSessionWrapper = OlmInboundGroupSessionWrapper(sessionKey, false)
|
||||
.apply {
|
||||
olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
|
||||
roomId = mxOlmInboundGroupSession2.mRoomId
|
||||
senderKey = mxOlmInboundGroupSession2.mSenderKey
|
||||
keysClaimed = mxOlmInboundGroupSession2.mKeysClaimed
|
||||
forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
|
||||
}
|
||||
|
||||
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Version 4L added Cross Signing info persistence
|
||||
private fun migrateTo4(realm: DynamicRealm) {
|
||||
Timber.d("Step 3 -> 4")
|
||||
|
||||
if (realm.schema.contains("TrustLevelEntity")) {
|
||||
Timber.d("Skipping Step 3 -> 4 because entities already exist")
|
||||
return
|
||||
}
|
||||
|
||||
Timber.d("Create KeyInfoEntity")
|
||||
val trustLevelEntityEntitySchema = realm.schema.create("TrustLevelEntity")
|
||||
.addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
|
||||
.setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true)
|
||||
.addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
|
||||
.setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
|
||||
|
||||
val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity")
|
||||
.addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java)
|
||||
.addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
|
||||
.addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
|
||||
.addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
|
||||
|
||||
Timber.d("Create CrossSigningInfoEntity")
|
||||
|
||||
val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity")
|
||||
.addField(CrossSigningInfoEntityFields.USER_ID, String::class.java)
|
||||
.addPrimaryKey(CrossSigningInfoEntityFields.USER_ID)
|
||||
.addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema)
|
||||
|
||||
Timber.d("Updating UserEntity table")
|
||||
realm.schema.get("UserEntity")
|
||||
?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema)
|
||||
|
||||
Timber.d("Updating CryptoMetadataEntity table")
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
|
||||
|
||||
val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
|
||||
val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
|
||||
List::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
|
||||
realm.schema.get("DeviceInfoEntity")
|
||||
?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
|
||||
?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true)
|
||||
?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
|
||||
?.transform { obj ->
|
||||
|
||||
try {
|
||||
val oldSerializedData = obj.getString("deviceInfoData")
|
||||
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { oldDevice ->
|
||||
|
||||
val trustLevel = realm.createObject("TrustLevelEntity")
|
||||
when (oldDevice.verified) {
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> {
|
||||
obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> {
|
||||
trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED)
|
||||
trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED)
|
||||
obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> {
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false)
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> {
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
}
|
||||
|
||||
obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId)
|
||||
obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey())
|
||||
obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms))
|
||||
obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys))
|
||||
obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures))
|
||||
obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned))
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.w(failure, "Crypto Data base migration error")
|
||||
// an unfortunate refactor did modify that class, making deserialization failing
|
||||
// so we just skip and ignore..
|
||||
}
|
||||
}
|
||||
?.removeField("deviceInfoData")
|
||||
}
|
||||
|
||||
private fun migrateTo5(realm: DynamicRealm) {
|
||||
Timber.d("Step 4 -> 5")
|
||||
realm.schema.remove("OutgoingRoomKeyRequestEntity")
|
||||
realm.schema.remove("IncomingRoomKeyRequestEntity")
|
||||
|
||||
// Not need to migrate existing request, just start fresh?
|
||||
|
||||
realm.schema.create("GossipingEventEntity")
|
||||
.addField(GossipingEventEntityFields.TYPE, String::class.java)
|
||||
.addIndex(GossipingEventEntityFields.TYPE)
|
||||
.addField(GossipingEventEntityFields.CONTENT, String::class.java)
|
||||
.addField(GossipingEventEntityFields.SENDER, String::class.java)
|
||||
.addIndex(GossipingEventEntityFields.SENDER)
|
||||
.addField(GossipingEventEntityFields.DECRYPTION_RESULT_JSON, String::class.java)
|
||||
.addField(GossipingEventEntityFields.DECRYPTION_ERROR_CODE, String::class.java)
|
||||
.addField(GossipingEventEntityFields.AGE_LOCAL_TS, Long::class.java)
|
||||
.setNullable(GossipingEventEntityFields.AGE_LOCAL_TS, true)
|
||||
.addField(GossipingEventEntityFields.SEND_STATE_STR, String::class.java)
|
||||
|
||||
realm.schema.create("IncomingGossipingRequestEntity")
|
||||
.addField(IncomingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
|
||||
.addIndex(IncomingGossipingRequestEntityFields.REQUEST_ID)
|
||||
.addField(IncomingGossipingRequestEntityFields.TYPE_STR, String::class.java)
|
||||
.addIndex(IncomingGossipingRequestEntityFields.TYPE_STR)
|
||||
.addField(IncomingGossipingRequestEntityFields.OTHER_USER_ID, String::class.java)
|
||||
.addField(IncomingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
|
||||
.addField(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, String::class.java)
|
||||
.addField(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
|
||||
.addField(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Long::class.java)
|
||||
.setNullable(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, true)
|
||||
|
||||
realm.schema.create("OutgoingGossipingRequestEntity")
|
||||
.addField(OutgoingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
|
||||
.addIndex(OutgoingGossipingRequestEntityFields.REQUEST_ID)
|
||||
.addField(OutgoingGossipingRequestEntityFields.RECIPIENTS_DATA, String::class.java)
|
||||
.addField(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
|
||||
.addField(OutgoingGossipingRequestEntityFields.TYPE_STR, String::class.java)
|
||||
.addIndex(OutgoingGossipingRequestEntityFields.TYPE_STR)
|
||||
.addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
|
||||
}
|
||||
|
||||
private fun migrateTo6(realm: DynamicRealm) {
|
||||
Timber.d("Step 5 -> 6")
|
||||
Timber.d("Updating CryptoMetadataEntity table")
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java)
|
||||
}
|
||||
|
||||
private fun migrateTo7(realm: DynamicRealm) {
|
||||
Timber.d("Step 6 -> 7")
|
||||
Timber.d("Updating KeyInfoEntity table")
|
||||
val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
|
||||
|
||||
val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
|
||||
try {
|
||||
keyInfoEntities.forEach {
|
||||
val stringSignatures = it.getString(KeyInfoEntityFields.SIGNATURES)
|
||||
val objectSignatures: Map<String, Map<String, String>>? = deserializeFromRealm(stringSignatures)
|
||||
val jsonSignatures = crossSigningKeysMapper.serializeSignatures(objectSignatures)
|
||||
it.setString(KeyInfoEntityFields.SIGNATURES, jsonSignatures)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
}
|
||||
|
||||
// Migrate frozen classes
|
||||
val inboundGroupSessions = realm.where("OlmInboundGroupSessionEntity").findAll()
|
||||
inboundGroupSessions.forEach { dynamicObject ->
|
||||
dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { serializedObject ->
|
||||
try {
|
||||
deserializeFromRealm<OlmInboundGroupSessionWrapper?>(serializedObject)?.let { oldFormat ->
|
||||
val newFormat = oldFormat.exportKeys()?.let {
|
||||
OlmInboundGroupSessionWrapper2(it)
|
||||
}
|
||||
dynamicObject.setString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA, serializeForRealm(newFormat))
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "## OlmInboundGroupSessionEntity migration failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo8(realm: DynamicRealm) {
|
||||
Timber.d("Step 7 -> 8")
|
||||
realm.schema.create("MyDeviceLastSeenInfoEntity")
|
||||
.addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java)
|
||||
.addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID)
|
||||
.addField(MyDeviceLastSeenInfoEntityFields.DISPLAY_NAME, String::class.java)
|
||||
.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_IP, String::class.java)
|
||||
.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, Long::class.java)
|
||||
.setNullable(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, true)
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
realm.schema.get("DeviceInfoEntity")
|
||||
?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
|
||||
?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
|
||||
?.transform { deviceInfoEntity ->
|
||||
tryOrNull {
|
||||
deviceInfoEntity.setLong(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fixes duplicate devices in UserEntity#devices
|
||||
private fun migrateTo9(realm: DynamicRealm) {
|
||||
Timber.d("Step 8 -> 9")
|
||||
val userEntities = realm.where("UserEntity").findAll()
|
||||
userEntities.forEach {
|
||||
try {
|
||||
val deviceList = it.getList(UserEntityFields.DEVICES.`$`)
|
||||
?: return@forEach
|
||||
val distinct = deviceList.distinctBy { it.getString(DeviceInfoEntityFields.DEVICE_ID) }
|
||||
if (distinct.size != deviceList.size) {
|
||||
deviceList.clear()
|
||||
deviceList.addAll(distinct)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.w(failure, "Crypto Data base migration error for migrateTo9")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Version 10L added WithHeld Keys Info (MSC2399)
|
||||
private fun migrateTo10(realm: DynamicRealm) {
|
||||
Timber.d("Step 9 -> 10")
|
||||
realm.schema.create("WithHeldSessionEntity")
|
||||
.addField(WithHeldSessionEntityFields.ROOM_ID, String::class.java)
|
||||
.addField(WithHeldSessionEntityFields.ALGORITHM, String::class.java)
|
||||
.addField(WithHeldSessionEntityFields.SESSION_ID, String::class.java)
|
||||
.addIndex(WithHeldSessionEntityFields.SESSION_ID)
|
||||
.addField(WithHeldSessionEntityFields.SENDER_KEY, String::class.java)
|
||||
.addIndex(WithHeldSessionEntityFields.SENDER_KEY)
|
||||
.addField(WithHeldSessionEntityFields.CODE_STRING, String::class.java)
|
||||
.addField(WithHeldSessionEntityFields.REASON, String::class.java)
|
||||
|
||||
realm.schema.create("SharedSessionEntity")
|
||||
.addField(SharedSessionEntityFields.ROOM_ID, String::class.java)
|
||||
.addField(SharedSessionEntityFields.ALGORITHM, String::class.java)
|
||||
.addField(SharedSessionEntityFields.SESSION_ID, String::class.java)
|
||||
.addIndex(SharedSessionEntityFields.SESSION_ID)
|
||||
.addField(SharedSessionEntityFields.USER_ID, String::class.java)
|
||||
.addIndex(SharedSessionEntityFields.USER_ID)
|
||||
.addField(SharedSessionEntityFields.DEVICE_ID, String::class.java)
|
||||
.addIndex(SharedSessionEntityFields.DEVICE_ID)
|
||||
.addField(SharedSessionEntityFields.CHAIN_INDEX, Long::class.java)
|
||||
.setNullable(SharedSessionEntityFields.CHAIN_INDEX, true)
|
||||
}
|
||||
|
||||
// Version 11L added deviceKeysSentToServer boolean to CryptoMetadataEntity
|
||||
private fun migrateTo11(realm: DynamicRealm) {
|
||||
Timber.d("Step 10 -> 11")
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
|
||||
}
|
||||
|
||||
// Version 12L added outbound group session persistence
|
||||
private fun migrateTo12(realm: DynamicRealm) {
|
||||
Timber.d("Step 11 -> 12")
|
||||
val outboundEntitySchema = realm.schema.create("OutboundGroupSessionInfoEntity")
|
||||
.addField(OutboundGroupSessionInfoEntityFields.SERIALIZED_OUTBOUND_SESSION_DATA, String::class.java)
|
||||
.addField(OutboundGroupSessionInfoEntityFields.CREATION_TIME, Long::class.java)
|
||||
.setNullable(OutboundGroupSessionInfoEntityFields.CREATION_TIME, true)
|
||||
|
||||
realm.schema.get("CryptoRoomEntity")
|
||||
?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
|
||||
}
|
||||
|
||||
// Version 13L delete unreferenced TrustLevelEntity
|
||||
private fun migrateTo13(realm: DynamicRealm) {
|
||||
Timber.d("Step 12 -> 13")
|
||||
|
||||
// Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366
|
||||
val trustLevelEntitySchema = realm.schema.get("TrustLevelEntity")
|
||||
|
||||
/*
|
||||
Creating a new temp field called isLinked which is set to true for those which are
|
||||
references by other objects. Rest of them are set to false. Then removing all
|
||||
those which are false and hence duplicate and unnecessary. Then removing the temp field
|
||||
isLinked
|
||||
*/
|
||||
var mainCounter = 0
|
||||
var deviceInfoCounter = 0
|
||||
var keyInfoCounter = 0
|
||||
val deleteCounter: Int
|
||||
|
||||
trustLevelEntitySchema
|
||||
?.addField("isLinked", Boolean::class.java)
|
||||
?.transform { obj ->
|
||||
// Setting to false for all by default
|
||||
obj.set("isLinked", false)
|
||||
mainCounter++
|
||||
}
|
||||
|
||||
realm.schema.get("DeviceInfoEntity")?.transform { obj ->
|
||||
// Setting to true for those which are referenced in DeviceInfoEntity
|
||||
deviceInfoCounter++
|
||||
obj.getObject("trustLevelEntity")?.set("isLinked", true)
|
||||
}
|
||||
|
||||
realm.schema.get("KeyInfoEntity")?.transform { obj ->
|
||||
// Setting to true for those which are referenced in KeyInfoEntity
|
||||
keyInfoCounter++
|
||||
obj.getObject("trustLevelEntity")?.set("isLinked", true)
|
||||
}
|
||||
|
||||
// Removing all those which are set as false
|
||||
realm.where("TrustLevelEntity")
|
||||
.equalTo("isLinked", false)
|
||||
.findAll()
|
||||
.also { deleteCounter = it.size }
|
||||
.deleteAllFromRealm()
|
||||
|
||||
trustLevelEntitySchema?.removeField("isLinked")
|
||||
|
||||
Timber.w("TrustLevelEntity cleanup: $mainCounter entities")
|
||||
Timber.w("TrustLevelEntity cleanup: $deviceInfoCounter entities referenced in DeviceInfoEntities")
|
||||
Timber.w("TrustLevelEntity cleanup: $keyInfoCounter entities referenced in KeyInfoEntity")
|
||||
Timber.w("TrustLevelEntity cleanup: $deleteCounter entities deleted!")
|
||||
if (mainCounter != deviceInfoCounter + keyInfoCounter + deleteCounter) {
|
||||
Timber.e("TrustLevelEntity cleanup: Something is not correct...")
|
||||
}
|
||||
}
|
||||
|
||||
// Version 14L Update the way we remember key sharing
|
||||
private fun migrateTo14(realm: DynamicRealm) {
|
||||
Timber.d("Step 13 -> 14")
|
||||
realm.schema.get("SharedSessionEntity")
|
||||
?.addField(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, String::class.java)
|
||||
?.addIndex(SharedSessionEntityFields.DEVICE_IDENTITY_KEY)
|
||||
?.transform {
|
||||
val sharedUserId = it.getString(SharedSessionEntityFields.USER_ID)
|
||||
val sharedDeviceId = it.getString(SharedSessionEntityFields.DEVICE_ID)
|
||||
val knownDevice = realm.where("DeviceInfoEntity")
|
||||
.equalTo(DeviceInfoEntityFields.USER_ID, sharedUserId)
|
||||
.equalTo(DeviceInfoEntityFields.DEVICE_ID, sharedDeviceId)
|
||||
.findFirst()
|
||||
it.setString(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, knownDevice?.getString(DeviceInfoEntityFields.IDENTITY_KEY))
|
||||
}
|
||||
if (oldVersion < 1) MigrateCryptoTo001Legacy(realm).perform()
|
||||
if (oldVersion < 2) MigrateCryptoTo002Legacy(realm).perform()
|
||||
if (oldVersion < 3) MigrateCryptoTo003RiotX(realm).perform()
|
||||
if (oldVersion < 4) MigrateCryptoTo004(realm).perform()
|
||||
if (oldVersion < 5) MigrateCryptoTo005(realm).perform()
|
||||
if (oldVersion < 6) MigrateCryptoTo006(realm).perform()
|
||||
if (oldVersion < 7) MigrateCryptoTo007(realm).perform()
|
||||
if (oldVersion < 8) MigrateCryptoTo008(realm).perform()
|
||||
if (oldVersion < 9) MigrateCryptoTo009(realm).perform()
|
||||
if (oldVersion < 10) MigrateCryptoTo010(realm).perform()
|
||||
if (oldVersion < 11) MigrateCryptoTo011(realm).perform()
|
||||
if (oldVersion < 12) MigrateCryptoTo012(realm).perform()
|
||||
if (oldVersion < 13) MigrateCryptoTo013(realm).perform()
|
||||
if (oldVersion < 14) MigrateCryptoTo014(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateCryptoTo001Legacy(realm: DynamicRealm) : RealmMigrator(realm, 1) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Add field lastReceivedMessageTs (Long) and set the value to 0")
|
||||
|
||||
realm.schema.get("OlmSessionEntity")
|
||||
?.addField(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Long::class.java)
|
||||
?.transform {
|
||||
it.setLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, 0)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateCryptoTo002Legacy(realm: DynamicRealm) : RealmMigrator(realm, 2) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
|
||||
realm.schema.get("IncomingRoomKeyRequestEntity")
|
||||
?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodySessionId", String::class.java)
|
||||
?.transform { dynamicObject ->
|
||||
try {
|
||||
val requestBodyString = dynamicObject.getString("requestBodyString")
|
||||
// It was a map before
|
||||
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
|
||||
|
||||
map?.let {
|
||||
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
|
||||
dynamicObject.setString("requestBodyRoomId", it["room_id"])
|
||||
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
|
||||
dynamicObject.setString("requestBodySessionId", it["session_id"])
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error")
|
||||
}
|
||||
}
|
||||
?.removeFieldIfExists("requestBodyString")
|
||||
|
||||
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
|
||||
realm.schema.get("OutgoingRoomKeyRequestEntity")
|
||||
?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
|
||||
?.addFieldIfNotExists("requestBodySessionId", String::class.java)
|
||||
?.transform { dynamicObject ->
|
||||
try {
|
||||
val requestBodyString = dynamicObject.getString("requestBodyString")
|
||||
// It was a map before
|
||||
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
|
||||
|
||||
map?.let {
|
||||
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
|
||||
dynamicObject.setString("requestBodyRoomId", it["room_id"])
|
||||
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
|
||||
dynamicObject.setString("requestBodySessionId", it["session_id"])
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error")
|
||||
}
|
||||
}
|
||||
?.removeFieldIfExists("requestBodyString")
|
||||
|
||||
Timber.d("Create KeysBackupDataEntity")
|
||||
if (!realm.schema.contains("KeysBackupDataEntity")) {
|
||||
realm.schema.create("KeysBackupDataEntity")
|
||||
.addField(KeysBackupDataEntityFields.PRIMARY_KEY, Integer::class.java)
|
||||
.addPrimaryKey(KeysBackupDataEntityFields.PRIMARY_KEY)
|
||||
.setRequired(KeysBackupDataEntityFields.PRIMARY_KEY, true)
|
||||
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_HASH, String::class.java)
|
||||
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_NUMBER_OF_KEYS, Integer::class.java)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import org.matrix.androidsdk.crypto.data.MXDeviceInfo
|
||||
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateCryptoTo003RiotX(realm: DynamicRealm) : RealmMigrator(realm, 3) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Migrate to RiotX model")
|
||||
realm.schema.get("CryptoRoomEntity")
|
||||
?.addFieldIfNotExists(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, Boolean::class.java)
|
||||
?.setRequiredIfNotAlready(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, false)
|
||||
|
||||
// Convert format of MXDeviceInfo, package has to be the same.
|
||||
realm.schema.get("DeviceInfoEntity")
|
||||
?.transform { obj ->
|
||||
try {
|
||||
val oldSerializedData = obj.getString("deviceInfoData")
|
||||
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { legacyMxDeviceInfo ->
|
||||
val newMxDeviceInfo = org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo(
|
||||
deviceId = legacyMxDeviceInfo.deviceId,
|
||||
userId = legacyMxDeviceInfo.userId,
|
||||
algorithms = legacyMxDeviceInfo.algorithms,
|
||||
keys = legacyMxDeviceInfo.keys,
|
||||
signatures = legacyMxDeviceInfo.signatures,
|
||||
unsigned = legacyMxDeviceInfo.unsigned,
|
||||
verified = legacyMxDeviceInfo.mVerified
|
||||
)
|
||||
|
||||
obj.setString("deviceInfoData", serializeForRealm(newMxDeviceInfo))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error")
|
||||
}
|
||||
}
|
||||
|
||||
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper
|
||||
realm.schema.get("OlmInboundGroupSessionEntity")
|
||||
?.transform { obj ->
|
||||
try {
|
||||
val oldSerializedData = obj.getString("olmInboundGroupSessionData")
|
||||
deserializeFromRealm<MXOlmInboundGroupSession2>(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
|
||||
val sessionKey = mxOlmInboundGroupSession2.mSession.sessionIdentifier()
|
||||
val newOlmInboundGroupSessionWrapper = OlmInboundGroupSessionWrapper(sessionKey, false)
|
||||
.apply {
|
||||
olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
|
||||
roomId = mxOlmInboundGroupSession2.mRoomId
|
||||
senderKey = mxOlmInboundGroupSession2.mSenderKey
|
||||
keysClaimed = mxOlmInboundGroupSession2.mKeysClaimed
|
||||
forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
|
||||
}
|
||||
|
||||
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
|
||||
import org.matrix.android.sdk.internal.di.SerializeNulls
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
// Version 4L added Cross Signing info persistence
|
||||
class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
if (realm.schema.contains("TrustLevelEntity")) {
|
||||
Timber.d("Skipping Step 3 -> 4 because entities already exist")
|
||||
return
|
||||
}
|
||||
|
||||
Timber.d("Create KeyInfoEntity")
|
||||
val trustLevelEntityEntitySchema = realm.schema.create("TrustLevelEntity")
|
||||
.addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
|
||||
.setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true)
|
||||
.addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
|
||||
.setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
|
||||
|
||||
val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity")
|
||||
.addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java)
|
||||
.addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
|
||||
.addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
|
||||
.addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
|
||||
|
||||
Timber.d("Create CrossSigningInfoEntity")
|
||||
|
||||
val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity")
|
||||
.addField(CrossSigningInfoEntityFields.USER_ID, String::class.java)
|
||||
.addPrimaryKey(CrossSigningInfoEntityFields.USER_ID)
|
||||
.addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema)
|
||||
|
||||
Timber.d("Updating UserEntity table")
|
||||
realm.schema.get("UserEntity")
|
||||
?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema)
|
||||
|
||||
Timber.d("Updating CryptoMetadataEntity table")
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
|
||||
|
||||
val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
|
||||
val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
|
||||
List::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
|
||||
realm.schema.get("DeviceInfoEntity")
|
||||
?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
|
||||
?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
|
||||
?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true)
|
||||
?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
|
||||
?.transform { obj ->
|
||||
|
||||
try {
|
||||
val oldSerializedData = obj.getString("deviceInfoData")
|
||||
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { oldDevice ->
|
||||
|
||||
val trustLevel = realm.createObject("TrustLevelEntity")
|
||||
when (oldDevice.verified) {
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> {
|
||||
obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> {
|
||||
trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED)
|
||||
trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED)
|
||||
obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> {
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false)
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> {
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
|
||||
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
|
||||
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
|
||||
}
|
||||
}
|
||||
|
||||
obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId)
|
||||
obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey())
|
||||
obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms))
|
||||
obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys))
|
||||
obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures))
|
||||
obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned))
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.w(failure, "Crypto Data base migration error")
|
||||
// an unfortunate refactor did modify that class, making deserialization failing
|
||||
// so we just skip and ignore..
|
||||
}
|
||||
}
|
||||
?.removeField("deviceInfoData")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateCryptoTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.remove("OutgoingRoomKeyRequestEntity")
|
||||
realm.schema.remove("IncomingRoomKeyRequestEntity")
|
||||
|
||||
// Not need to migrate existing request, just start fresh?
|
||||
|
||||
realm.schema.create("GossipingEventEntity")
|
||||
.addField(GossipingEventEntityFields.TYPE, String::class.java)
|
||||
.addIndex(GossipingEventEntityFields.TYPE)
|
||||
.addField(GossipingEventEntityFields.CONTENT, String::class.java)
|
||||
.addField(GossipingEventEntityFields.SENDER, String::class.java)
|
||||
.addIndex(GossipingEventEntityFields.SENDER)
|
||||
.addField(GossipingEventEntityFields.DECRYPTION_RESULT_JSON, String::class.java)
|
||||
.addField(GossipingEventEntityFields.DECRYPTION_ERROR_CODE, String::class.java)
|
||||
.addField(GossipingEventEntityFields.AGE_LOCAL_TS, Long::class.java)
|
||||
.setNullable(GossipingEventEntityFields.AGE_LOCAL_TS, true)
|
||||
.addField(GossipingEventEntityFields.SEND_STATE_STR, String::class.java)
|
||||
|
||||
realm.schema.create("IncomingGossipingRequestEntity")
|
||||
.addField(IncomingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
|
||||
.addIndex(IncomingGossipingRequestEntityFields.REQUEST_ID)
|
||||
.addField(IncomingGossipingRequestEntityFields.TYPE_STR, String::class.java)
|
||||
.addIndex(IncomingGossipingRequestEntityFields.TYPE_STR)
|
||||
.addField(IncomingGossipingRequestEntityFields.OTHER_USER_ID, String::class.java)
|
||||
.addField(IncomingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
|
||||
.addField(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, String::class.java)
|
||||
.addField(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
|
||||
.addField(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Long::class.java)
|
||||
.setNullable(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, true)
|
||||
|
||||
realm.schema.create("OutgoingGossipingRequestEntity")
|
||||
.addField(OutgoingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
|
||||
.addIndex(OutgoingGossipingRequestEntityFields.REQUEST_ID)
|
||||
.addField(OutgoingGossipingRequestEntityFields.RECIPIENTS_DATA, String::class.java)
|
||||
.addField(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
|
||||
.addField(OutgoingGossipingRequestEntityFields.TYPE_STR, String::class.java)
|
||||
.addIndex(OutgoingGossipingRequestEntityFields.TYPE_STR)
|
||||
.addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateCryptoTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Updating CryptoMetadataEntity table")
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java)
|
||||
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateCryptoTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Updating KeyInfoEntity table")
|
||||
val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
|
||||
|
||||
val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
|
||||
try {
|
||||
keyInfoEntities.forEach {
|
||||
val stringSignatures = it.getString(KeyInfoEntityFields.SIGNATURES)
|
||||
val objectSignatures: Map<String, Map<String, String>>? = deserializeFromRealm(stringSignatures)
|
||||
val jsonSignatures = crossSigningKeysMapper.serializeSignatures(objectSignatures)
|
||||
it.setString(KeyInfoEntityFields.SIGNATURES, jsonSignatures)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
}
|
||||
|
||||
// Migrate frozen classes
|
||||
val inboundGroupSessions = realm.where("OlmInboundGroupSessionEntity").findAll()
|
||||
inboundGroupSessions.forEach { dynamicObject ->
|
||||
dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { serializedObject ->
|
||||
try {
|
||||
deserializeFromRealm<OlmInboundGroupSessionWrapper?>(serializedObject)?.let { oldFormat ->
|
||||
val newFormat = oldFormat.exportKeys()?.let {
|
||||
OlmInboundGroupSessionWrapper2(it)
|
||||
}
|
||||
dynamicObject.setString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA, serializeForRealm(newFormat))
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "## OlmInboundGroupSessionEntity migration failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.create("MyDeviceLastSeenInfoEntity")
|
||||
.addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java)
|
||||
.addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID)
|
||||
.addField(MyDeviceLastSeenInfoEntityFields.DISPLAY_NAME, String::class.java)
|
||||
.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_IP, String::class.java)
|
||||
.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, Long::class.java)
|
||||
.setNullable(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, true)
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
realm.schema.get("DeviceInfoEntity")
|
||||
?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
|
||||
?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
|
||||
?.transform { deviceInfoEntity ->
|
||||
tryOrNull {
|
||||
deviceInfoEntity.setLong(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, now)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
// Fixes duplicate devices in UserEntity#devices
|
||||
class MigrateCryptoTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
val userEntities = realm.where("UserEntity").findAll()
|
||||
userEntities.forEach {
|
||||
try {
|
||||
val deviceList = it.getList(UserEntityFields.DEVICES.`$`)
|
||||
?: return@forEach
|
||||
val distinct = deviceList.distinctBy { it.getString(DeviceInfoEntityFields.DEVICE_ID) }
|
||||
if (distinct.size != deviceList.size) {
|
||||
deviceList.clear()
|
||||
deviceList.addAll(distinct)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.w(failure, "Crypto Data base migration error for migrateTo9")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
// Version 10L added WithHeld Keys Info (MSC2399)
|
||||
class MigrateCryptoTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.create("WithHeldSessionEntity")
|
||||
.addField(WithHeldSessionEntityFields.ROOM_ID, String::class.java)
|
||||
.addField(WithHeldSessionEntityFields.ALGORITHM, String::class.java)
|
||||
.addField(WithHeldSessionEntityFields.SESSION_ID, String::class.java)
|
||||
.addIndex(WithHeldSessionEntityFields.SESSION_ID)
|
||||
.addField(WithHeldSessionEntityFields.SENDER_KEY, String::class.java)
|
||||
.addIndex(WithHeldSessionEntityFields.SENDER_KEY)
|
||||
.addField(WithHeldSessionEntityFields.CODE_STRING, String::class.java)
|
||||
.addField(WithHeldSessionEntityFields.REASON, String::class.java)
|
||||
|
||||
realm.schema.create("SharedSessionEntity")
|
||||
.addField(SharedSessionEntityFields.ROOM_ID, String::class.java)
|
||||
.addField(SharedSessionEntityFields.ALGORITHM, String::class.java)
|
||||
.addField(SharedSessionEntityFields.SESSION_ID, String::class.java)
|
||||
.addIndex(SharedSessionEntityFields.SESSION_ID)
|
||||
.addField(SharedSessionEntityFields.USER_ID, String::class.java)
|
||||
.addIndex(SharedSessionEntityFields.USER_ID)
|
||||
.addField(SharedSessionEntityFields.DEVICE_ID, String::class.java)
|
||||
.addIndex(SharedSessionEntityFields.DEVICE_ID)
|
||||
.addField(SharedSessionEntityFields.CHAIN_INDEX, Long::class.java)
|
||||
.setNullable(SharedSessionEntityFields.CHAIN_INDEX, true)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
// Version 11L added deviceKeysSentToServer boolean to CryptoMetadataEntity
|
||||
class MigrateCryptoTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
// Version 12L added outbound group session persistence
|
||||
class MigrateCryptoTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
val outboundEntitySchema = realm.schema.create("OutboundGroupSessionInfoEntity")
|
||||
.addField(OutboundGroupSessionInfoEntityFields.SERIALIZED_OUTBOUND_SESSION_DATA, String::class.java)
|
||||
.addField(OutboundGroupSessionInfoEntityFields.CREATION_TIME, Long::class.java)
|
||||
.setNullable(OutboundGroupSessionInfoEntityFields.CREATION_TIME, true)
|
||||
|
||||
realm.schema.get("CryptoRoomEntity")
|
||||
?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
// Version 13L delete unreferenced TrustLevelEntity
|
||||
class MigrateCryptoTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
// Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366
|
||||
val trustLevelEntitySchema = realm.schema.get("TrustLevelEntity")
|
||||
|
||||
/*
|
||||
Creating a new temp field called isLinked which is set to true for those which are
|
||||
references by other objects. Rest of them are set to false. Then removing all
|
||||
those which are false and hence duplicate and unnecessary. Then removing the temp field
|
||||
isLinked
|
||||
*/
|
||||
var mainCounter = 0
|
||||
var deviceInfoCounter = 0
|
||||
var keyInfoCounter = 0
|
||||
val deleteCounter: Int
|
||||
|
||||
trustLevelEntitySchema
|
||||
?.addField("isLinked", Boolean::class.java)
|
||||
?.transform { obj ->
|
||||
// Setting to false for all by default
|
||||
obj.set("isLinked", false)
|
||||
mainCounter++
|
||||
}
|
||||
|
||||
realm.schema.get("DeviceInfoEntity")?.transform { obj ->
|
||||
// Setting to true for those which are referenced in DeviceInfoEntity
|
||||
deviceInfoCounter++
|
||||
obj.getObject("trustLevelEntity")?.set("isLinked", true)
|
||||
}
|
||||
|
||||
realm.schema.get("KeyInfoEntity")?.transform { obj ->
|
||||
// Setting to true for those which are referenced in KeyInfoEntity
|
||||
keyInfoCounter++
|
||||
obj.getObject("trustLevelEntity")?.set("isLinked", true)
|
||||
}
|
||||
|
||||
// Removing all those which are set as false
|
||||
realm.where("TrustLevelEntity")
|
||||
.equalTo("isLinked", false)
|
||||
.findAll()
|
||||
.also { deleteCounter = it.size }
|
||||
.deleteAllFromRealm()
|
||||
|
||||
trustLevelEntitySchema?.removeField("isLinked")
|
||||
|
||||
Timber.w("TrustLevelEntity cleanup: $mainCounter entities")
|
||||
Timber.w("TrustLevelEntity cleanup: $deviceInfoCounter entities referenced in DeviceInfoEntities")
|
||||
Timber.w("TrustLevelEntity cleanup: $keyInfoCounter entities referenced in KeyInfoEntity")
|
||||
Timber.w("TrustLevelEntity cleanup: $deleteCounter entities deleted!")
|
||||
if (mainCounter != deviceInfoCounter + keyInfoCounter + deleteCounter) {
|
||||
Timber.e("TrustLevelEntity cleanup: Something is not correct...")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
// Version 14L Update the way we remember key sharing
|
||||
class MigrateCryptoTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("SharedSessionEntity")
|
||||
?.addField(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, String::class.java)
|
||||
?.addIndex(SharedSessionEntityFields.DEVICE_IDENTITY_KEY)
|
||||
?.transform {
|
||||
val sharedUserId = it.getString(SharedSessionEntityFields.USER_ID)
|
||||
val sharedDeviceId = it.getString(SharedSessionEntityFields.DEVICE_ID)
|
||||
val knownDevice = realm.where("DeviceInfoEntity")
|
||||
.equalTo(DeviceInfoEntityFields.USER_ID, sharedUserId)
|
||||
.equalTo(DeviceInfoEntityFields.DEVICE_ID, sharedDeviceId)
|
||||
.findFirst()
|
||||
it.setString(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, knownDevice?.getString(DeviceInfoEntityFields.IDENTITY_KEY))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,37 +17,31 @@
|
|||
package org.matrix.android.sdk.internal.database
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.FieldAttribute
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
|
||||
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo001
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo002
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo003
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo004
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo005
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo006
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo007
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo008
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo009
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo010
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo011
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo012
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo013
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo014
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo015
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo016
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo017
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo018
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo019
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo020
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo021
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo022
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -55,11 +49,6 @@ import javax.inject.Inject
|
|||
internal class RealmSessionStoreMigration @Inject constructor(
|
||||
private val normalizer: Normalizer
|
||||
) : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 26L
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces all RealmSessionStoreMigration instances to be equal
|
||||
* Avoids Realm throwing when multiple instances of the migration are set
|
||||
|
@ -67,437 +56,34 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
override fun equals(other: Any?) = other is RealmSessionStoreMigration
|
||||
override fun hashCode() = 1000
|
||||
|
||||
val schemaVersion = 24L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
|
||||
Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
if (oldVersion <= 1) migrateTo2(realm)
|
||||
if (oldVersion <= 2) migrateTo3(realm)
|
||||
if (oldVersion <= 3) migrateTo4(realm)
|
||||
if (oldVersion <= 4) migrateTo5(realm)
|
||||
if (oldVersion <= 5) migrateTo6(realm)
|
||||
if (oldVersion <= 6) migrateTo7(realm)
|
||||
if (oldVersion <= 7) migrateTo8(realm)
|
||||
if (oldVersion <= 8) migrateTo9(realm)
|
||||
if (oldVersion <= 9) migrateTo10(realm)
|
||||
if (oldVersion <= 10) migrateTo11(realm)
|
||||
if (oldVersion <= 11) migrateTo12(realm)
|
||||
if (oldVersion <= 12) migrateTo13(realm)
|
||||
if (oldVersion <= 13) migrateTo14(realm)
|
||||
if (oldVersion <= 14) migrateTo15(realm)
|
||||
if (oldVersion <= 15) migrateTo16(realm)
|
||||
if (oldVersion <= 16) migrateTo17(realm)
|
||||
if (oldVersion <= 17) migrateTo18(realm)
|
||||
if (oldVersion <= 18) migrateTo19(realm)
|
||||
if (oldVersion <= 19) migrateTo20(realm)
|
||||
if (oldVersion <= 20) migrateTo21(realm)
|
||||
if (oldVersion <= 21) migrateTo22(realm)
|
||||
if (oldVersion <= 22) migrateTo23(realm)
|
||||
if (oldVersion <= 23) migrateTo24(realm)
|
||||
if (oldVersion <= 24) migrateTo25(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
Timber.d("Step 0 -> 1")
|
||||
// Add hasFailedSending in RoomSummary and a small warning icon on room list
|
||||
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.HAS_FAILED_SENDING, Boolean::class.java)
|
||||
?.transform { obj ->
|
||||
obj.setBoolean(RoomSummaryEntityFields.HAS_FAILED_SENDING, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo2(realm: DynamicRealm) {
|
||||
Timber.d("Step 1 -> 2")
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField("adminE2EByDefault", Boolean::class.java)
|
||||
?.transform { obj ->
|
||||
obj.setBoolean("adminE2EByDefault", true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo3(realm: DynamicRealm) {
|
||||
Timber.d("Step 2 -> 3")
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField("preferredJitsiDomain", String::class.java)
|
||||
?.transform { obj ->
|
||||
// Schedule a refresh of the capabilities
|
||||
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo4(realm: DynamicRealm) {
|
||||
Timber.d("Step 3 -> 4")
|
||||
realm.schema.create("PendingThreePidEntity")
|
||||
.addField(PendingThreePidEntityFields.CLIENT_SECRET, String::class.java)
|
||||
.setRequired(PendingThreePidEntityFields.CLIENT_SECRET, true)
|
||||
.addField(PendingThreePidEntityFields.EMAIL, String::class.java)
|
||||
.addField(PendingThreePidEntityFields.MSISDN, String::class.java)
|
||||
.addField(PendingThreePidEntityFields.SEND_ATTEMPT, Int::class.java)
|
||||
.addField(PendingThreePidEntityFields.SID, String::class.java)
|
||||
.setRequired(PendingThreePidEntityFields.SID, true)
|
||||
.addField(PendingThreePidEntityFields.SUBMIT_URL, String::class.java)
|
||||
}
|
||||
|
||||
private fun migrateTo5(realm: DynamicRealm) {
|
||||
Timber.d("Step 4 -> 5")
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.removeField("adminE2EByDefault")
|
||||
?.removeField("preferredJitsiDomain")
|
||||
}
|
||||
|
||||
private fun migrateTo6(realm: DynamicRealm) {
|
||||
Timber.d("Step 5 -> 6")
|
||||
realm.schema.create("PreviewUrlCacheEntity")
|
||||
.addField(PreviewUrlCacheEntityFields.URL, String::class.java)
|
||||
.setRequired(PreviewUrlCacheEntityFields.URL, true)
|
||||
.addPrimaryKey(PreviewUrlCacheEntityFields.URL)
|
||||
.addField(PreviewUrlCacheEntityFields.URL_FROM_SERVER, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.SITE_NAME, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.TITLE, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.DESCRIPTION, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java)
|
||||
}
|
||||
|
||||
private fun migrateTo7(realm: DynamicRealm) {
|
||||
Timber.d("Step 6 -> 7")
|
||||
realm.schema.get("RoomEntity")
|
||||
?.addField(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, String::class.java)
|
||||
?.transform { obj ->
|
||||
if (obj.getBoolean("areAllMembersLoaded")) {
|
||||
obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.LOADED.name)
|
||||
} else {
|
||||
obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.NONE.name)
|
||||
}
|
||||
}
|
||||
?.removeField("areAllMembersLoaded")
|
||||
}
|
||||
|
||||
private fun migrateTo8(realm: DynamicRealm) {
|
||||
Timber.d("Step 7 -> 8")
|
||||
|
||||
val editionOfEventSchema = realm.schema.create("EditionOfEvent")
|
||||
.addField(EditionOfEventFields.CONTENT, String::class.java)
|
||||
.addField(EditionOfEventFields.EVENT_ID, String::class.java)
|
||||
.setRequired(EditionOfEventFields.EVENT_ID, true)
|
||||
.addField(EditionOfEventFields.SENDER_ID, String::class.java)
|
||||
.setRequired(EditionOfEventFields.SENDER_ID, true)
|
||||
.addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
|
||||
.addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)
|
||||
|
||||
realm.schema.get("EditAggregatedSummaryEntity")
|
||||
?.removeField("aggregatedContent")
|
||||
?.removeField("sourceEvents")
|
||||
?.removeField("lastEditTs")
|
||||
?.removeField("sourceLocalEchoEvents")
|
||||
?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
|
||||
|
||||
// This has to be done once a parent use the model as a child
|
||||
// See https://github.com/realm/realm-java/issues/7402
|
||||
editionOfEventSchema.isEmbedded = true
|
||||
}
|
||||
|
||||
private fun migrateTo9(realm: DynamicRealm) {
|
||||
Timber.d("Step 8 -> 9")
|
||||
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Long::class.java, FieldAttribute.INDEXED)
|
||||
?.setNullable(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, true)
|
||||
?.addIndex(RoomSummaryEntityFields.MEMBERSHIP_STR)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_DIRECT)
|
||||
?.addIndex(RoomSummaryEntityFields.VERSIONING_STATE_STR)
|
||||
|
||||
?.addField(RoomSummaryEntityFields.IS_FAVOURITE, Boolean::class.java)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_FAVOURITE)
|
||||
?.addField(RoomSummaryEntityFields.IS_LOW_PRIORITY, Boolean::class.java)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_LOW_PRIORITY)
|
||||
?.addField(RoomSummaryEntityFields.IS_SERVER_NOTICE, Boolean::class.java)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
|
||||
|
||||
?.transform { obj ->
|
||||
val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
|
||||
}
|
||||
obj.setBoolean(RoomSummaryEntityFields.IS_FAVOURITE, isFavorite)
|
||||
|
||||
val isLowPriority = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_LOW_PRIORITY
|
||||
}
|
||||
|
||||
obj.setBoolean(RoomSummaryEntityFields.IS_LOW_PRIORITY, isLowPriority)
|
||||
|
||||
// XXX migrate last message origin server ts
|
||||
obj.getObject(RoomSummaryEntityFields.LATEST_PREVIEWABLE_EVENT.`$`)
|
||||
?.getObject(TimelineEventEntityFields.ROOT.`$`)
|
||||
?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
|
||||
obj.setLong(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo10(realm: DynamicRealm) {
|
||||
Timber.d("Step 9 -> 10")
|
||||
realm.schema.create("SpaceChildSummaryEntity")
|
||||
?.addField(SpaceChildSummaryEntityFields.ORDER, String::class.java)
|
||||
?.addField(SpaceChildSummaryEntityFields.CHILD_ROOM_ID, String::class.java)
|
||||
?.addField(SpaceChildSummaryEntityFields.AUTO_JOIN, Boolean::class.java)
|
||||
?.setNullable(SpaceChildSummaryEntityFields.AUTO_JOIN, true)
|
||||
?.addRealmObjectField(SpaceChildSummaryEntityFields.CHILD_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
|
||||
?.addRealmListField(SpaceChildSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
|
||||
|
||||
realm.schema.create("SpaceParentSummaryEntity")
|
||||
?.addField(SpaceParentSummaryEntityFields.PARENT_ROOM_ID, String::class.java)
|
||||
?.addField(SpaceParentSummaryEntityFields.CANONICAL, Boolean::class.java)
|
||||
?.setNullable(SpaceParentSummaryEntityFields.CANONICAL, true)
|
||||
?.addRealmObjectField(SpaceParentSummaryEntityFields.PARENT_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
|
||||
?.addRealmListField(SpaceParentSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
|
||||
|
||||
val creationContentAdapter = MoshiProvider.providesMoshi().adapter(RoomCreateContent::class.java)
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
|
||||
?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, String::class.java)
|
||||
?.addField(RoomSummaryEntityFields.GROUP_IDS, String::class.java)
|
||||
?.transform { obj ->
|
||||
|
||||
val creationEvent = realm.where("CurrentStateEventEntity")
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_CREATE)
|
||||
.findFirst()
|
||||
|
||||
val roomType = creationEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
|
||||
?.getString(EventEntityFields.CONTENT)?.let {
|
||||
creationContentAdapter.fromJson(it)?.type
|
||||
}
|
||||
|
||||
obj.setString(RoomSummaryEntityFields.ROOM_TYPE, roomType)
|
||||
}
|
||||
?.addRealmListField(RoomSummaryEntityFields.PARENTS.`$`, realm.schema.get("SpaceParentSummaryEntity")!!)
|
||||
?.addRealmListField(RoomSummaryEntityFields.CHILDREN.`$`, realm.schema.get("SpaceChildSummaryEntity")!!)
|
||||
}
|
||||
|
||||
private fun migrateTo11(realm: DynamicRealm) {
|
||||
Timber.d("Step 10 -> 11")
|
||||
realm.schema.get("EventEntity")
|
||||
?.addField(EventEntityFields.SEND_STATE_DETAILS, String::class.java)
|
||||
}
|
||||
|
||||
private fun migrateTo12(realm: DynamicRealm) {
|
||||
Timber.d("Step 11 -> 12")
|
||||
|
||||
val joinRulesContentAdapter = MoshiProvider.providesMoshi().adapter(RoomJoinRulesContent::class.java)
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.JOIN_RULES_STR, String::class.java)
|
||||
?.transform { obj ->
|
||||
val joinRulesEvent = realm.where("CurrentStateEventEntity")
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_JOIN_RULES)
|
||||
.findFirst()
|
||||
|
||||
val roomJoinRules = joinRulesEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
|
||||
?.getString(EventEntityFields.CONTENT)?.let {
|
||||
joinRulesContentAdapter.fromJson(it)?.joinRules
|
||||
}
|
||||
|
||||
obj.setString(RoomSummaryEntityFields.JOIN_RULES_STR, roomJoinRules?.name)
|
||||
}
|
||||
|
||||
realm.schema.get("SpaceChildSummaryEntity")
|
||||
?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
|
||||
?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
|
||||
}
|
||||
|
||||
private fun migrateTo13(realm: DynamicRealm) {
|
||||
Timber.d("Step 12 -> 13")
|
||||
// Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12()
|
||||
realm.schema.get("SpaceChildSummaryEntity")
|
||||
?.takeIf { !it.hasField(SpaceChildSummaryEntityFields.SUGGESTED) }
|
||||
?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
|
||||
?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
|
||||
}
|
||||
|
||||
private fun migrateTo14(realm: DynamicRealm) {
|
||||
Timber.d("Step 13 -> 14")
|
||||
val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
|
||||
.addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
|
||||
.addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
|
||||
|
||||
realm.schema.get("RoomEntity")
|
||||
?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
|
||||
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, Boolean::class.java, FieldAttribute.INDEXED)
|
||||
?.transform {
|
||||
val isHiddenFromUser = it.getString(RoomSummaryEntityFields.VERSIONING_STATE_STR) == VersioningState.UPGRADED_ROOM_JOINED.name
|
||||
it.setBoolean(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, isHiddenFromUser)
|
||||
}
|
||||
|
||||
roomAccountDataSchema.isEmbedded = true
|
||||
}
|
||||
|
||||
private fun migrateTo15(realm: DynamicRealm) {
|
||||
Timber.d("Step 14 -> 15")
|
||||
// fix issue with flattenParentIds on DM that kept growing with duplicate
|
||||
// so we reset it, will be updated next sync
|
||||
realm.where("RoomSummaryEntity")
|
||||
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||
.findAll()
|
||||
.onEach {
|
||||
it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo16(realm: DynamicRealm) {
|
||||
Timber.d("Step 15 -> 16")
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java)
|
||||
?.transform { obj ->
|
||||
// Schedule a refresh of the capabilities
|
||||
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo17(realm: DynamicRealm) {
|
||||
Timber.d("Step 16 -> 17")
|
||||
realm.schema.get("EventInsertEntity")
|
||||
?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java)
|
||||
}
|
||||
|
||||
private fun migrateTo18(realm: DynamicRealm) {
|
||||
Timber.d("Step 17 -> 18")
|
||||
realm.schema.create("UserPresenceEntity")
|
||||
?.addField(UserPresenceEntityFields.USER_ID, String::class.java)
|
||||
?.addPrimaryKey(UserPresenceEntityFields.USER_ID)
|
||||
?.setRequired(UserPresenceEntityFields.USER_ID, true)
|
||||
?.addField(UserPresenceEntityFields.PRESENCE_STR, String::class.java)
|
||||
?.addField(UserPresenceEntityFields.LAST_ACTIVE_AGO, Long::class.java)
|
||||
?.setNullable(UserPresenceEntityFields.LAST_ACTIVE_AGO, true)
|
||||
?.addField(UserPresenceEntityFields.STATUS_MESSAGE, String::class.java)
|
||||
?.addField(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, Boolean::class.java)
|
||||
?.setNullable(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, true)
|
||||
?.addField(UserPresenceEntityFields.AVATAR_URL, String::class.java)
|
||||
?.addField(UserPresenceEntityFields.DISPLAY_NAME, String::class.java)
|
||||
|
||||
val userPresenceEntity = realm.schema.get("UserPresenceEntity") ?: return
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addRealmObjectField(RoomSummaryEntityFields.DIRECT_USER_PRESENCE.`$`, userPresenceEntity)
|
||||
|
||||
realm.schema.get("RoomMemberSummaryEntity")
|
||||
?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity)
|
||||
}
|
||||
|
||||
private fun migrateTo19(realm: DynamicRealm) {
|
||||
Timber.d("Step 18 -> 19")
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, String::class.java)
|
||||
?.transform {
|
||||
it.getString(RoomSummaryEntityFields.DISPLAY_NAME)?.let { displayName ->
|
||||
val normalised = normalizer.normalize(displayName)
|
||||
it.set(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, normalised)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo20(realm: DynamicRealm) {
|
||||
Timber.d("Step 19 -> 20")
|
||||
|
||||
realm.schema.get("ChunkEntity")?.apply {
|
||||
if (hasField("numberOfTimelineEvents")) {
|
||||
removeField("numberOfTimelineEvents")
|
||||
}
|
||||
var cleanOldChunks = false
|
||||
if (!hasField(ChunkEntityFields.NEXT_CHUNK.`$`)) {
|
||||
cleanOldChunks = true
|
||||
addRealmObjectField(ChunkEntityFields.NEXT_CHUNK.`$`, this)
|
||||
}
|
||||
if (!hasField(ChunkEntityFields.PREV_CHUNK.`$`)) {
|
||||
cleanOldChunks = true
|
||||
addRealmObjectField(ChunkEntityFields.PREV_CHUNK.`$`, this)
|
||||
}
|
||||
if (cleanOldChunks) {
|
||||
val chunkEntities = realm.where("ChunkEntity").equalTo(ChunkEntityFields.IS_LAST_FORWARD, false).findAll()
|
||||
chunkEntities.deleteAllFromRealm()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo21(realm: DynamicRealm) {
|
||||
Timber.d("Step 20 -> 21")
|
||||
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.E2E_ALGORITHM, String::class.java)
|
||||
?.transform { obj ->
|
||||
|
||||
val encryptionContentAdapter = MoshiProvider.providesMoshi().adapter(EncryptionEventContent::class.java)
|
||||
|
||||
val encryptionEvent = realm.where("CurrentStateEventEntity")
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION)
|
||||
.findFirst()
|
||||
|
||||
val encryptionEventRoot = encryptionEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
|
||||
val algorithm = encryptionEventRoot
|
||||
?.getString(EventEntityFields.CONTENT)?.let {
|
||||
encryptionContentAdapter.fromJson(it)?.algorithm
|
||||
}
|
||||
|
||||
obj.setString(RoomSummaryEntityFields.E2E_ALGORITHM, algorithm)
|
||||
obj.setBoolean(RoomSummaryEntityFields.IS_ENCRYPTED, encryptionEvent != null)
|
||||
encryptionEventRoot?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
|
||||
obj.setLong(RoomSummaryEntityFields.ENCRYPTION_EVENT_TS, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo22(realm: DynamicRealm) {
|
||||
Timber.d("Step 21 -> 22")
|
||||
val listJoinedRoomIds = realm.where("RoomEntity")
|
||||
.equalTo(RoomEntityFields.MEMBERSHIP_STR, Membership.JOIN.name).findAll()
|
||||
.map { it.getString(RoomEntityFields.ROOM_ID) }
|
||||
|
||||
val hasMissingStateEvent = realm.where("CurrentStateEventEntity")
|
||||
.`in`(CurrentStateEventEntityFields.ROOM_ID, listJoinedRoomIds.toTypedArray())
|
||||
.isNull(CurrentStateEventEntityFields.ROOT.`$`).findFirst() != null
|
||||
|
||||
if (hasMissingStateEvent) {
|
||||
Timber.v("Has some missing state event, clear session cache")
|
||||
realm.deleteAll()
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo23(realm: DynamicRealm) {
|
||||
Timber.d("Step 22 -> 23")
|
||||
val eventEntity = realm.schema.get("TimelineEventEntity") ?: return
|
||||
|
||||
realm.schema.get("EventEntity")
|
||||
?.addField(EventEntityFields.IS_ROOT_THREAD, Boolean::class.java, FieldAttribute.INDEXED)
|
||||
?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED)
|
||||
?.addField(EventEntityFields.NUMBER_OF_THREADS, Int::class.java)
|
||||
?.addField(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, String::class.java)
|
||||
?.transform {
|
||||
it.setString(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NO_NEW_MESSAGE.name)
|
||||
}
|
||||
?.addRealmObjectField(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.`$`, eventEntity)
|
||||
}
|
||||
|
||||
private fun migrateTo24(realm: DynamicRealm) {
|
||||
Timber.d("Step 23 -> 24")
|
||||
realm.schema.get("PreviewUrlCacheEntity")
|
||||
?.addField(PreviewUrlCacheEntityFields.IMAGE_WIDTH, Int::class.java)
|
||||
?.setNullable(PreviewUrlCacheEntityFields.IMAGE_WIDTH, true)
|
||||
?.addField(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, Int::class.java)
|
||||
?.setNullable(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, true)
|
||||
}
|
||||
|
||||
private fun migrateTo25(realm: DynamicRealm) {
|
||||
Timber.d("Step 24 -> 25")
|
||||
realm.schema.get("ChunkEntity")
|
||||
?.addField(ChunkEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED)
|
||||
?.addField(ChunkEntityFields.IS_LAST_FORWARD_THREAD, Boolean::class.java, FieldAttribute.INDEXED)
|
||||
|
||||
realm.schema.get("TimelineEventEntity")
|
||||
?.addField(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, Boolean::class.java)
|
||||
if (oldVersion < 1) MigrateSessionTo001(realm).perform()
|
||||
if (oldVersion < 2) MigrateSessionTo002(realm).perform()
|
||||
if (oldVersion < 3) MigrateSessionTo003(realm).perform()
|
||||
if (oldVersion < 4) MigrateSessionTo004(realm).perform()
|
||||
if (oldVersion < 5) MigrateSessionTo005(realm).perform()
|
||||
if (oldVersion < 6) MigrateSessionTo006(realm).perform()
|
||||
if (oldVersion < 7) MigrateSessionTo007(realm).perform()
|
||||
if (oldVersion < 8) MigrateSessionTo008(realm).perform()
|
||||
if (oldVersion < 9) MigrateSessionTo009(realm).perform()
|
||||
if (oldVersion < 10) MigrateSessionTo010(realm).perform()
|
||||
if (oldVersion < 11) MigrateSessionTo011(realm).perform()
|
||||
if (oldVersion < 12) MigrateSessionTo012(realm).perform()
|
||||
if (oldVersion < 13) MigrateSessionTo013(realm).perform()
|
||||
if (oldVersion < 14) MigrateSessionTo014(realm).perform()
|
||||
if (oldVersion < 15) MigrateSessionTo015(realm).perform()
|
||||
if (oldVersion < 16) MigrateSessionTo016(realm).perform()
|
||||
if (oldVersion < 17) MigrateSessionTo017(realm).perform()
|
||||
if (oldVersion < 18) MigrateSessionTo018(realm).perform()
|
||||
if (oldVersion < 19) MigrateSessionTo019(realm, normalizer).perform()
|
||||
if (oldVersion < 20) MigrateSessionTo020(realm).perform()
|
||||
if (oldVersion < 21) MigrateSessionTo021(realm).perform()
|
||||
if (oldVersion < 22) MigrateSessionTo022(realm).perform()
|
||||
if (oldVersion < 23) MigrateSessionTo023(realm).perform()
|
||||
if (oldVersion < 24) MigrateSessionTo024(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
|||
}
|
||||
.allowWritesOnUiThread(true)
|
||||
.modules(SessionRealmModule())
|
||||
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
|
||||
.schemaVersion(realmSessionStoreMigration.schemaVersion)
|
||||
.migration(realmSessionStoreMigration)
|
||||
.build()
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
// Add hasFailedSending in RoomSummary and a small warning icon on room list
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.HAS_FAILED_SENDING, Boolean::class.java)
|
||||
?.transform { obj ->
|
||||
obj.setBoolean(RoomSummaryEntityFields.HAS_FAILED_SENDING, false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo002(realm: DynamicRealm) : RealmMigrator(realm, 2) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField("adminE2EByDefault", Boolean::class.java)
|
||||
?.transform { obj ->
|
||||
obj.setBoolean("adminE2EByDefault", true)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField("preferredJitsiDomain", String::class.java)
|
||||
?.transform { obj ->
|
||||
// Schedule a refresh of the capabilities
|
||||
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.create("PendingThreePidEntity")
|
||||
.addField(PendingThreePidEntityFields.CLIENT_SECRET, String::class.java)
|
||||
.setRequired(PendingThreePidEntityFields.CLIENT_SECRET, true)
|
||||
.addField(PendingThreePidEntityFields.EMAIL, String::class.java)
|
||||
.addField(PendingThreePidEntityFields.MSISDN, String::class.java)
|
||||
.addField(PendingThreePidEntityFields.SEND_ATTEMPT, Int::class.java)
|
||||
.addField(PendingThreePidEntityFields.SID, String::class.java)
|
||||
.setRequired(PendingThreePidEntityFields.SID, true)
|
||||
.addField(PendingThreePidEntityFields.SUBMIT_URL, String::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.removeField("adminE2EByDefault")
|
||||
?.removeField("preferredJitsiDomain")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.create("PreviewUrlCacheEntity")
|
||||
.addField(PreviewUrlCacheEntityFields.URL, String::class.java)
|
||||
.setRequired(PreviewUrlCacheEntityFields.URL, true)
|
||||
.addPrimaryKey(PreviewUrlCacheEntityFields.URL)
|
||||
.addField(PreviewUrlCacheEntityFields.URL_FROM_SERVER, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.SITE_NAME, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.TITLE, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.DESCRIPTION, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java)
|
||||
.addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("RoomEntity")
|
||||
?.addField(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, String::class.java)
|
||||
?.transform { obj ->
|
||||
if (obj.getBoolean("areAllMembersLoaded")) {
|
||||
obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.LOADED.name)
|
||||
} else {
|
||||
obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.NONE.name)
|
||||
}
|
||||
}
|
||||
?.removeField("areAllMembersLoaded")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
val editionOfEventSchema = realm.schema.create("EditionOfEvent")
|
||||
.addField(EditionOfEventFields.CONTENT, String::class.java)
|
||||
.addField(EditionOfEventFields.EVENT_ID, String::class.java)
|
||||
.setRequired(EditionOfEventFields.EVENT_ID, true)
|
||||
.addField(EditionOfEventFields.SENDER_ID, String::class.java)
|
||||
.setRequired(EditionOfEventFields.SENDER_ID, true)
|
||||
.addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
|
||||
.addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)
|
||||
|
||||
realm.schema.get("EditAggregatedSummaryEntity")
|
||||
?.removeField("aggregatedContent")
|
||||
?.removeField("sourceEvents")
|
||||
?.removeField("lastEditTs")
|
||||
?.removeField("sourceLocalEchoEvents")
|
||||
?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
|
||||
|
||||
// This has to be done once a parent use the model as a child
|
||||
// See https://github.com/realm/realm-java/issues/7402
|
||||
editionOfEventSchema.isEmbedded = true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.FieldAttribute
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Long::class.java, FieldAttribute.INDEXED)
|
||||
?.setNullable(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, true)
|
||||
?.addIndex(RoomSummaryEntityFields.MEMBERSHIP_STR)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_DIRECT)
|
||||
?.addIndex(RoomSummaryEntityFields.VERSIONING_STATE_STR)
|
||||
|
||||
?.addField(RoomSummaryEntityFields.IS_FAVOURITE, Boolean::class.java)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_FAVOURITE)
|
||||
?.addField(RoomSummaryEntityFields.IS_LOW_PRIORITY, Boolean::class.java)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_LOW_PRIORITY)
|
||||
?.addField(RoomSummaryEntityFields.IS_SERVER_NOTICE, Boolean::class.java)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
|
||||
|
||||
?.transform { obj ->
|
||||
val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
|
||||
}
|
||||
obj.setBoolean(RoomSummaryEntityFields.IS_FAVOURITE, isFavorite)
|
||||
|
||||
val isLowPriority = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_LOW_PRIORITY
|
||||
}
|
||||
|
||||
obj.setBoolean(RoomSummaryEntityFields.IS_LOW_PRIORITY, isLowPriority)
|
||||
|
||||
// XXX migrate last message origin server ts
|
||||
obj.getObject(RoomSummaryEntityFields.LATEST_PREVIEWABLE_EVENT.`$`)
|
||||
?.getObject(TimelineEventEntityFields.ROOT.`$`)
|
||||
?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
|
||||
obj.setLong(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.create("SpaceChildSummaryEntity")
|
||||
?.addField(SpaceChildSummaryEntityFields.ORDER, String::class.java)
|
||||
?.addField(SpaceChildSummaryEntityFields.CHILD_ROOM_ID, String::class.java)
|
||||
?.addField(SpaceChildSummaryEntityFields.AUTO_JOIN, Boolean::class.java)
|
||||
?.setNullable(SpaceChildSummaryEntityFields.AUTO_JOIN, true)
|
||||
?.addRealmObjectField(SpaceChildSummaryEntityFields.CHILD_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
|
||||
?.addRealmListField(SpaceChildSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
|
||||
|
||||
realm.schema.create("SpaceParentSummaryEntity")
|
||||
?.addField(SpaceParentSummaryEntityFields.PARENT_ROOM_ID, String::class.java)
|
||||
?.addField(SpaceParentSummaryEntityFields.CANONICAL, Boolean::class.java)
|
||||
?.setNullable(SpaceParentSummaryEntityFields.CANONICAL, true)
|
||||
?.addRealmObjectField(SpaceParentSummaryEntityFields.PARENT_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
|
||||
?.addRealmListField(SpaceParentSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
|
||||
|
||||
val creationContentAdapter = MoshiProvider.providesMoshi().adapter(RoomCreateContent::class.java)
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
|
||||
?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, String::class.java)
|
||||
?.addField(RoomSummaryEntityFields.GROUP_IDS, String::class.java)
|
||||
?.transform { obj ->
|
||||
|
||||
val creationEvent = realm.where("CurrentStateEventEntity")
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_CREATE)
|
||||
.findFirst()
|
||||
|
||||
val roomType = creationEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
|
||||
?.getString(EventEntityFields.CONTENT)?.let {
|
||||
creationContentAdapter.fromJson(it)?.type
|
||||
}
|
||||
|
||||
obj.setString(RoomSummaryEntityFields.ROOM_TYPE, roomType)
|
||||
}
|
||||
?.addRealmListField(RoomSummaryEntityFields.PARENTS.`$`, realm.schema.get("SpaceParentSummaryEntity")!!)
|
||||
?.addRealmListField(RoomSummaryEntityFields.CHILDREN.`$`, realm.schema.get("SpaceChildSummaryEntity")!!)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("EventEntity")
|
||||
?.addField(EventEntityFields.SEND_STATE_DETAILS, String::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
val joinRulesContentAdapter = MoshiProvider.providesMoshi().adapter(RoomJoinRulesContent::class.java)
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.JOIN_RULES_STR, String::class.java)
|
||||
?.transform { obj ->
|
||||
val joinRulesEvent = realm.where("CurrentStateEventEntity")
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_JOIN_RULES)
|
||||
.findFirst()
|
||||
|
||||
val roomJoinRules = joinRulesEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
|
||||
?.getString(EventEntityFields.CONTENT)?.let {
|
||||
joinRulesContentAdapter.fromJson(it)?.joinRules
|
||||
}
|
||||
|
||||
obj.setString(RoomSummaryEntityFields.JOIN_RULES_STR, roomJoinRules?.name)
|
||||
}
|
||||
|
||||
realm.schema.get("SpaceChildSummaryEntity")
|
||||
?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
|
||||
?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
// Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12()
|
||||
realm.schema.get("SpaceChildSummaryEntity")
|
||||
?.takeIf { !it.hasField(SpaceChildSummaryEntityFields.SUGGESTED) }
|
||||
?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
|
||||
?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.FieldAttribute
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
|
||||
.addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
|
||||
.addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
|
||||
|
||||
realm.schema.get("RoomEntity")
|
||||
?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
|
||||
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, Boolean::class.java, FieldAttribute.INDEXED)
|
||||
?.transform {
|
||||
val isHiddenFromUser = it.getString(RoomSummaryEntityFields.VERSIONING_STATE_STR) == VersioningState.UPGRADED_ROOM_JOINED.name
|
||||
it.setBoolean(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, isHiddenFromUser)
|
||||
}
|
||||
|
||||
roomAccountDataSchema.isEmbedded = true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
// fix issue with flattenParentIds on DM that kept growing with duplicate
|
||||
// so we reset it, will be updated next sync
|
||||
realm.where("RoomSummaryEntity")
|
||||
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||
.findAll()
|
||||
.onEach {
|
||||
it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo016(realm: DynamicRealm) : RealmMigrator(realm, 16) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java)
|
||||
?.transform { obj ->
|
||||
// Schedule a refresh of the capabilities
|
||||
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("EventInsertEntity")
|
||||
?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo018(realm: DynamicRealm) : RealmMigrator(realm, 18) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.create("UserPresenceEntity")
|
||||
?.addField(UserPresenceEntityFields.USER_ID, String::class.java)
|
||||
?.addPrimaryKey(UserPresenceEntityFields.USER_ID)
|
||||
?.setRequired(UserPresenceEntityFields.USER_ID, true)
|
||||
?.addField(UserPresenceEntityFields.PRESENCE_STR, String::class.java)
|
||||
?.addField(UserPresenceEntityFields.LAST_ACTIVE_AGO, Long::class.java)
|
||||
?.setNullable(UserPresenceEntityFields.LAST_ACTIVE_AGO, true)
|
||||
?.addField(UserPresenceEntityFields.STATUS_MESSAGE, String::class.java)
|
||||
?.addField(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, Boolean::class.java)
|
||||
?.setNullable(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, true)
|
||||
?.addField(UserPresenceEntityFields.AVATAR_URL, String::class.java)
|
||||
?.addField(UserPresenceEntityFields.DISPLAY_NAME, String::class.java)
|
||||
|
||||
val userPresenceEntity = realm.schema.get("UserPresenceEntity") ?: return
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addRealmObjectField(RoomSummaryEntityFields.DIRECT_USER_PRESENCE.`$`, userPresenceEntity)
|
||||
|
||||
realm.schema.get("RoomMemberSummaryEntity")
|
||||
?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo019(realm: DynamicRealm,
|
||||
private val normalizer: Normalizer) : RealmMigrator(realm, 19) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, String::class.java)
|
||||
?.transform {
|
||||
it.getString(RoomSummaryEntityFields.DISPLAY_NAME)?.let { displayName ->
|
||||
val normalised = normalizer.normalize(displayName)
|
||||
it.set(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, normalised)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo020(realm: DynamicRealm) : RealmMigrator(realm, 20) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("ChunkEntity")?.apply {
|
||||
if (hasField("numberOfTimelineEvents")) {
|
||||
removeField("numberOfTimelineEvents")
|
||||
}
|
||||
var cleanOldChunks = false
|
||||
if (!hasField(ChunkEntityFields.NEXT_CHUNK.`$`)) {
|
||||
cleanOldChunks = true
|
||||
addRealmObjectField(ChunkEntityFields.NEXT_CHUNK.`$`, this)
|
||||
}
|
||||
if (!hasField(ChunkEntityFields.PREV_CHUNK.`$`)) {
|
||||
cleanOldChunks = true
|
||||
addRealmObjectField(ChunkEntityFields.PREV_CHUNK.`$`, this)
|
||||
}
|
||||
if (cleanOldChunks) {
|
||||
val chunkEntities = realm.where("ChunkEntity").equalTo(ChunkEntityFields.IS_LAST_FORWARD, false).findAll()
|
||||
chunkEntities.deleteAllFromRealm()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo021(realm: DynamicRealm) : RealmMigrator(realm, 21) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.E2E_ALGORITHM, String::class.java)
|
||||
?.transform { obj ->
|
||||
|
||||
val encryptionContentAdapter = MoshiProvider.providesMoshi().adapter(EncryptionEventContent::class.java)
|
||||
|
||||
val encryptionEvent = realm.where("CurrentStateEventEntity")
|
||||
.equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
|
||||
.equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION)
|
||||
.findFirst()
|
||||
|
||||
val encryptionEventRoot = encryptionEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
|
||||
val algorithm = encryptionEventRoot
|
||||
?.getString(EventEntityFields.CONTENT)?.let {
|
||||
encryptionContentAdapter.fromJson(it)?.algorithm
|
||||
}
|
||||
|
||||
obj.setString(RoomSummaryEntityFields.E2E_ALGORITHM, algorithm)
|
||||
obj.setBoolean(RoomSummaryEntityFields.IS_ENCRYPTED, encryptionEvent != null)
|
||||
encryptionEventRoot?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
|
||||
obj.setLong(RoomSummaryEntityFields.ENCRYPTION_EVENT_TS, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateSessionTo022(realm: DynamicRealm) : RealmMigrator(realm, 22) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
val listJoinedRoomIds = realm.where("RoomEntity")
|
||||
.equalTo(RoomEntityFields.MEMBERSHIP_STR, Membership.JOIN.name).findAll()
|
||||
.map { it.getString(RoomEntityFields.ROOM_ID) }
|
||||
|
||||
val hasMissingStateEvent = realm.where("CurrentStateEventEntity")
|
||||
.`in`(CurrentStateEventEntityFields.ROOM_ID, listJoinedRoomIds.toTypedArray())
|
||||
.isNull(CurrentStateEventEntityFields.ROOT.`$`).findFirst() != null
|
||||
|
||||
if (hasMissingStateEvent) {
|
||||
Timber.v("Has some missing state event, clear session cache")
|
||||
realm.deleteAll()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.FieldAttribute
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo023(realm: DynamicRealm) : RealmMigrator(realm, 23) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
val eventEntity = realm.schema.get("TimelineEventEntity") ?: return
|
||||
|
||||
realm.schema.get("EventEntity")
|
||||
?.addField(EventEntityFields.IS_ROOT_THREAD, Boolean::class.java, FieldAttribute.INDEXED)
|
||||
?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED)
|
||||
?.addField(EventEntityFields.NUMBER_OF_THREADS, Int::class.java)
|
||||
?.addField(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, String::class.java)
|
||||
?.transform {
|
||||
it.setString(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NO_NEW_MESSAGE.name)
|
||||
}
|
||||
?.addRealmObjectField(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.`$`, eventEntity)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo024(realm: DynamicRealm) : RealmMigrator(realm, 24) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("PreviewUrlCacheEntity")
|
||||
?.addField(PreviewUrlCacheEntityFields.IMAGE_WIDTH, Int::class.java)
|
||||
?.setNullable(PreviewUrlCacheEntityFields.IMAGE_WIDTH, true)
|
||||
?.addField(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, Int::class.java)
|
||||
?.setNullable(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, true)
|
||||
}
|
||||
}
|
|
@ -42,7 +42,8 @@ import org.matrix.android.sdk.internal.legacy.riot.HomeServerConnectionConfig as
|
|||
internal class DefaultLegacySessionImporter @Inject constructor(
|
||||
private val context: Context,
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val realmKeysUtils: RealmKeysUtils
|
||||
private val realmKeysUtils: RealmKeysUtils,
|
||||
private val realmCryptoStoreMigration: RealmCryptoStoreMigration
|
||||
) : LegacySessionImporter {
|
||||
|
||||
private val loginStorage = LoginStorage(context)
|
||||
|
@ -170,8 +171,8 @@ internal class DefaultLegacySessionImporter @Inject constructor(
|
|||
.directory(File(context.filesDir, userMd5))
|
||||
.name("crypto_store.realm")
|
||||
.modules(RealmCryptoStoreModule())
|
||||
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
|
||||
.migration(RealmCryptoStoreMigration)
|
||||
.schemaVersion(realmCryptoStoreMigration.schemaVersion)
|
||||
.migration(realmCryptoStoreMigration)
|
||||
.build()
|
||||
|
||||
Timber.d("Migration: copy DB to encrypted DB")
|
||||
|
|
|
@ -18,24 +18,23 @@ package org.matrix.android.sdk.internal.raw
|
|||
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntityFields
|
||||
import org.matrix.android.sdk.internal.raw.migration.MigrateGlobalTo001
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object GlobalRealmMigration : RealmMigration {
|
||||
internal class GlobalRealmMigration @Inject constructor() : RealmMigration {
|
||||
/**
|
||||
* Forces all GlobalRealmMigration instances to be equal
|
||||
* Avoids Realm throwing when multiple instances of the migration are set
|
||||
*/
|
||||
override fun equals(other: Any?) = other is GlobalRealmMigration
|
||||
override fun hashCode() = 2000
|
||||
|
||||
// Current schema version
|
||||
const val SCHEMA_VERSION = 1L
|
||||
val schemaVersion = 1L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
||||
Timber.d("Migrating Global Realm from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
realm.schema.create("KnownServerUrlEntity")
|
||||
.addField(KnownServerUrlEntityFields.URL, String::class.java)
|
||||
.addPrimaryKey(KnownServerUrlEntityFields.URL)
|
||||
.setRequired(KnownServerUrlEntityFields.URL, true)
|
||||
if (oldVersion < 1) MigrateGlobalTo001(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,14 +51,15 @@ internal abstract class RawModule {
|
|||
@Provides
|
||||
@GlobalDatabase
|
||||
@MatrixScope
|
||||
fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||
fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
||||
globalRealmMigration: GlobalRealmMigration): RealmConfiguration {
|
||||
return RealmConfiguration.Builder()
|
||||
.apply {
|
||||
realmKeysUtils.configureEncryption(this, DB_ALIAS)
|
||||
}
|
||||
.name("matrix-sdk-global.realm")
|
||||
.schemaVersion(GlobalRealmMigration.SCHEMA_VERSION)
|
||||
.migration(GlobalRealmMigration)
|
||||
.schemaVersion(globalRealmMigration.schemaVersion)
|
||||
.migration(globalRealmMigration)
|
||||
.allowWritesOnUiThread(true)
|
||||
.modules(GlobalRealmModule())
|
||||
.build()
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.raw.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateGlobalTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.create("KnownServerUrlEntity")
|
||||
.addField(KnownServerUrlEntityFields.URL, String::class.java)
|
||||
.addPrimaryKey(KnownServerUrlEntityFields.URL)
|
||||
.setRequired(KnownServerUrlEntityFields.URL, true)
|
||||
}
|
||||
}
|
|
@ -94,12 +94,12 @@ internal class CleanupSession @Inject constructor(
|
|||
do {
|
||||
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
|
||||
val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration)
|
||||
Timber.d("Wait for all Realm instance to be closed ($sessionRealmCount - $cryptoRealmCount)")
|
||||
if (sessionRealmCount > 0 || cryptoRealmCount > 0) {
|
||||
Timber.d("Waiting ${TIME_TO_WAIT_MILLIS}ms")
|
||||
Timber.d("Waiting ${TIME_TO_WAIT_MILLIS}ms for all Realm instance to be closed ($sessionRealmCount - $cryptoRealmCount)")
|
||||
delay(TIME_TO_WAIT_MILLIS)
|
||||
timeToWaitMillis -= TIME_TO_WAIT_MILLIS
|
||||
} else {
|
||||
Timber.d("Finished waiting for all Realm instance to be closed ($sessionRealmCount - $cryptoRealmCount)")
|
||||
timeToWaitMillis = 0
|
||||
}
|
||||
} while (timeToWaitMillis > 0)
|
||||
|
|
|
@ -60,6 +60,7 @@ internal abstract class IdentityModule {
|
|||
@IdentityDatabase
|
||||
@SessionScope
|
||||
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
||||
realmIdentityStoreMigration: RealmIdentityStoreMigration,
|
||||
@SessionFilesDirectory directory: File,
|
||||
@UserMd5 userMd5: String): RealmConfiguration {
|
||||
return RealmConfiguration.Builder()
|
||||
|
@ -68,8 +69,8 @@ internal abstract class IdentityModule {
|
|||
.apply {
|
||||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||
}
|
||||
.schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION)
|
||||
.migration(RealmIdentityStoreMigration)
|
||||
.schemaVersion(realmIdentityStoreMigration.schemaVersion)
|
||||
.migration(realmIdentityStoreMigration)
|
||||
.allowWritesOnUiThread(true)
|
||||
.modules(IdentityRealmModule())
|
||||
.build()
|
||||
|
|
|
@ -18,23 +18,23 @@ package org.matrix.android.sdk.internal.session.identity.db
|
|||
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.internal.session.identity.db.migration.MigrateIdentityTo001
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object RealmIdentityStoreMigration : RealmMigration {
|
||||
internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration {
|
||||
/**
|
||||
* Forces all RealmIdentityStoreMigration instances to be equal
|
||||
* Avoids Realm throwing when multiple instances of the migration are set
|
||||
*/
|
||||
override fun equals(other: Any?) = other is RealmIdentityStoreMigration
|
||||
override fun hashCode() = 3000
|
||||
|
||||
const val IDENTITY_STORE_SCHEMA_VERSION = 1L
|
||||
val schemaVersion = 1L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.v("Migrating Realm Identity from $oldVersion to $newVersion")
|
||||
Timber.d("Migrating Realm Identity from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
Timber.d("Step 0 -> 1")
|
||||
Timber.d("Add field userConsent (Boolean) and set the value to false")
|
||||
|
||||
realm.schema.get("IdentityDataEntity")
|
||||
?.addField(IdentityDataEntityFields.USER_CONSENT, Boolean::class.java)
|
||||
if (oldVersion < 1) MigrateIdentityTo001(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.identity.db.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.session.identity.db.IdentityDataEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
import timber.log.Timber
|
||||
|
||||
class MigrateIdentityTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
Timber.d("Add field userConsent (Boolean) and set the value to false")
|
||||
realm.schema.get("IdentityDataEntity")
|
||||
?.addField(IdentityDataEntityFields.USER_CONSENT, Boolean::class.java)
|
||||
}
|
||||
}
|
|
@ -66,11 +66,11 @@ internal class UIEchoManager(private val listener: Listener) {
|
|||
return existingState != sendState
|
||||
}
|
||||
|
||||
fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean {
|
||||
fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean {
|
||||
when (timelineEvent.root.getClearType()) {
|
||||
EventType.REDACTION -> {
|
||||
}
|
||||
EventType.REACTION -> {
|
||||
EventType.REACTION -> {
|
||||
val content: ReactionContent? = timelineEvent.root.content?.toModel<ReactionContent>()
|
||||
if (RelationType.ANNOTATION == content?.relatesTo?.type) {
|
||||
val reaction = content.relatesTo.key
|
||||
|
@ -104,8 +104,8 @@ internal class UIEchoManager(private val listener: Listener) {
|
|||
val updateReactions = existingAnnotationSummary.reactionsSummary.toMutableList()
|
||||
|
||||
contents.forEach { uiEchoReaction ->
|
||||
val existing = updateReactions.firstOrNull { it.key == uiEchoReaction.reaction }
|
||||
if (existing == null) {
|
||||
val indexOfExistingReaction = updateReactions.indexOfFirst { it.key == uiEchoReaction.reaction }
|
||||
if (indexOfExistingReaction == -1) {
|
||||
// just add the new key
|
||||
ReactionAggregatedSummary(
|
||||
key = uiEchoReaction.reaction,
|
||||
|
@ -117,6 +117,7 @@ internal class UIEchoManager(private val listener: Listener) {
|
|||
).let { updateReactions.add(it) }
|
||||
} else {
|
||||
// update Existing Key
|
||||
val existing = updateReactions[indexOfExistingReaction]
|
||||
if (!existing.localEchoEvents.contains(uiEchoReaction.localEchoId)) {
|
||||
updateReactions.remove(existing)
|
||||
// only update if echo is not yet there
|
||||
|
@ -128,7 +129,7 @@ internal class UIEchoManager(private val listener: Listener) {
|
|||
sourceEvents = existing.sourceEvents,
|
||||
localEchoEvents = existing.localEchoEvents + uiEchoReaction.localEchoId
|
||||
|
||||
).let { updateReactions.add(it) }
|
||||
).let { updateReactions.add(indexOfExistingReaction, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.util.database
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmObjectSchema
|
||||
import timber.log.Timber
|
||||
|
||||
abstract class RealmMigrator(private val realm: DynamicRealm,
|
||||
private val targetSchemaVersion: Int) {
|
||||
fun perform() {
|
||||
Timber.d("Migrate ${realm.configuration.realmFileName} to $targetSchemaVersion")
|
||||
doMigrate(realm)
|
||||
}
|
||||
|
||||
abstract fun doMigrate(realm: DynamicRealm)
|
||||
|
||||
protected fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
|
||||
if (!hasField(fieldName)) {
|
||||
addField(fieldName, fieldType)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
protected fun RealmObjectSchema.removeFieldIfExists(fieldName: String): RealmObjectSchema {
|
||||
if (hasField(fieldName)) {
|
||||
removeField(fieldName)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
protected fun RealmObjectSchema.setRequiredIfNotAlready(fieldName: String, isRequired: Boolean): RealmObjectSchema {
|
||||
if (isRequired != isRequired(fieldName)) {
|
||||
setRequired(fieldName, isRequired)
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
|
@ -2475,9 +2475,11 @@
|
|||
"b": "1F636-200D-1F32B-FE0F",
|
||||
"j": [
|
||||
"absentminded",
|
||||
"face in clouds",
|
||||
"face in the fog",
|
||||
"head in clouds"
|
||||
"head in clouds",
|
||||
"shower",
|
||||
"steam",
|
||||
"dream"
|
||||
]
|
||||
},
|
||||
"smirking-face": {
|
||||
|
@ -2536,12 +2538,14 @@
|
|||
"b": "1F62E-200D-1F4A8",
|
||||
"j": [
|
||||
"exhale",
|
||||
"face exhaling",
|
||||
"gasp",
|
||||
"groan",
|
||||
"relief",
|
||||
"whisper",
|
||||
"whistle"
|
||||
"whistle",
|
||||
"relieve",
|
||||
"tired",
|
||||
"sigh"
|
||||
]
|
||||
},
|
||||
"lying-face": {
|
||||
|
@ -2745,11 +2749,15 @@
|
|||
"b": "1F635-200D-1F4AB",
|
||||
"j": [
|
||||
"dizzy",
|
||||
"face with spiral eyes",
|
||||
"hypnotized",
|
||||
"spiral",
|
||||
"trouble",
|
||||
"whoa"
|
||||
"whoa",
|
||||
"sick",
|
||||
"ill",
|
||||
"confused",
|
||||
"nauseous",
|
||||
"nausea"
|
||||
]
|
||||
},
|
||||
"exploding-head": {
|
||||
|
@ -3704,10 +3712,11 @@
|
|||
"j": [
|
||||
"burn",
|
||||
"heart",
|
||||
"heart on fire",
|
||||
"love",
|
||||
"lust",
|
||||
"sacred heart"
|
||||
"sacred heart",
|
||||
"passionate",
|
||||
"enthusiastic"
|
||||
]
|
||||
},
|
||||
"mending-heart": {
|
||||
|
@ -3717,10 +3726,12 @@
|
|||
"healthier",
|
||||
"improving",
|
||||
"mending",
|
||||
"mending heart",
|
||||
"recovering",
|
||||
"recuperating",
|
||||
"well"
|
||||
"well",
|
||||
"broken heart",
|
||||
"bandage",
|
||||
"wounded"
|
||||
]
|
||||
},
|
||||
"red-heart": {
|
||||
|
@ -4748,7 +4759,8 @@
|
|||
"j": [
|
||||
"beard",
|
||||
"man",
|
||||
"man: beard"
|
||||
"man: beard",
|
||||
"facial hair"
|
||||
]
|
||||
},
|
||||
"woman-beard": {
|
||||
|
@ -4757,7 +4769,8 @@
|
|||
"j": [
|
||||
"beard",
|
||||
"woman",
|
||||
"woman: beard"
|
||||
"woman: beard",
|
||||
"facial hair"
|
||||
]
|
||||
},
|
||||
"man-red-hair": {
|
||||
|
|
|
@ -376,7 +376,7 @@ dependencies {
|
|||
implementation 'com.facebook.stetho:stetho:1.6.0'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.42'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.43'
|
||||
|
||||
// FlowBinding
|
||||
implementation libs.github.flowBinding
|
||||
|
|
|
@ -18,6 +18,9 @@ package im.vector.app.core.extensions
|
|||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ImageSpan
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
|
@ -34,6 +37,16 @@ fun Context.singletonEntryPoint(): SingletonEntryPoint {
|
|||
return EntryPoints.get(applicationContext, SingletonEntryPoint::class.java)
|
||||
}
|
||||
|
||||
fun Context.getDrawableAsSpannable(@DrawableRes drawableRes: Int, alignment: Int = ImageSpan.ALIGN_BOTTOM): Spannable {
|
||||
return SpannableString(" ").apply {
|
||||
val span = ContextCompat.getDrawable(this@getDrawableAsSpannable, drawableRes)?.let {
|
||||
it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
|
||||
ImageSpan(it, alignment)
|
||||
}
|
||||
setSpan(span, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getResTintedDrawable(@DrawableRes drawableRes: Int, @ColorRes tint: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1f): Drawable? {
|
||||
return getTintedDrawable(drawableRes, ContextCompat.getColor(this, tint), alpha)
|
||||
}
|
||||
|
|
|
@ -1915,6 +1915,10 @@ class TimelineFragment @Inject constructor(
|
|||
timelineViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction))
|
||||
}
|
||||
|
||||
override fun onAddMoreReaction(event: TimelineEvent) {
|
||||
openEmojiReactionPicker(event.eventId)
|
||||
}
|
||||
|
||||
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View, isRootThreadEvent: Boolean) {
|
||||
when (messageContent) {
|
||||
is MessageVerificationRequestContent -> {
|
||||
|
@ -2119,7 +2123,7 @@ class TimelineFragment @Inject constructor(
|
|||
openRoomMemberProfile(action.userId)
|
||||
}
|
||||
is EventSharedAction.AddReaction -> {
|
||||
emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId))
|
||||
openEmojiReactionPicker(action.eventId)
|
||||
}
|
||||
is EventSharedAction.ViewReactions -> {
|
||||
ViewReactionsBottomSheet.newInstance(timelineArgs.roomId, action.messageInformationData)
|
||||
|
@ -2241,6 +2245,10 @@ class TimelineFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun openEmojiReactionPicker(eventId: String) {
|
||||
emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), eventId))
|
||||
}
|
||||
|
||||
private fun askConfirmationToEndPoll(eventId: String) {
|
||||
MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog)
|
||||
.setTitle(R.string.end_poll_confirmation_title)
|
||||
|
|
|
@ -41,6 +41,7 @@ import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFact
|
|||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.ReactionsSummaryFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
||||
|
@ -86,7 +87,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
@TimelineEventControllerHandler
|
||||
private val backgroundHandler: Handler,
|
||||
private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper,
|
||||
private val readReceiptsItemFactory: ReadReceiptsItemFactory
|
||||
private val readReceiptsItemFactory: ReadReceiptsItemFactory,
|
||||
private val reactionListFactory: ReactionsSummaryFactory
|
||||
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
|
||||
|
||||
/**
|
||||
|
@ -138,6 +140,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
fun getPreviewUrlRetriever(): PreviewUrlRetriever
|
||||
|
||||
fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)
|
||||
|
||||
fun onAddMoreReaction(event: TimelineEvent)
|
||||
}
|
||||
|
||||
interface ReactionPillCallback {
|
||||
|
@ -283,6 +287,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
super.onAttachedToRecyclerView(recyclerView)
|
||||
timeline?.addListener(this)
|
||||
timelineMediaSizeProvider.recyclerView = recyclerView
|
||||
reactionListFactory.onRequestBuild = { requestModelBuild() }
|
||||
}
|
||||
|
||||
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||
|
@ -290,6 +295,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
contentUploadStateTrackerBinder.clear()
|
||||
contentDownloadStateTrackerBinder.clear()
|
||||
timeline?.removeListener(this)
|
||||
reactionListFactory.onRequestBuild = null
|
||||
super.onDetachedFromRecyclerView(recyclerView)
|
||||
}
|
||||
|
||||
|
@ -383,7 +389,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
val event = currentSnapshot[position]
|
||||
val nextEvent = currentSnapshot.nextOrNull(position)
|
||||
// Should be build if not cached or if model should be refreshed
|
||||
if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) {
|
||||
if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false || reactionListFactory.needsRebuild(event)) {
|
||||
val prevEvent = currentSnapshot.prevOrNull(position)
|
||||
val prevDisplayableEvent = currentSnapshot.subList(0, position).lastOrNull {
|
||||
timelineEventVisibilityHelper.shouldShowEvent(
|
||||
|
|
|
@ -24,7 +24,6 @@ import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
|||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
|
||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
|
||||
|
@ -50,7 +49,8 @@ import javax.inject.Inject
|
|||
*/
|
||||
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val messageLayoutFactory: TimelineMessageLayoutFactory) {
|
||||
private val messageLayoutFactory: TimelineMessageLayoutFactory,
|
||||
private val reactionsSummaryFactory: ReactionsSummaryFactory) {
|
||||
|
||||
fun create(params: TimelineItemFactoryParams): MessageInformationData {
|
||||
val event = params.event
|
||||
|
@ -93,11 +93,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||
avatarUrl = event.senderInfo.avatarUrl,
|
||||
memberName = event.senderInfo.disambiguatedDisplayName,
|
||||
messageLayout = messageLayout,
|
||||
orderedReactionList = event.annotations?.reactionsSummary
|
||||
// ?.filter { isSingleEmoji(it.key) }
|
||||
?.map {
|
||||
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
|
||||
},
|
||||
reactionsSummary = reactionsSummaryFactory.create(event, params.callback),
|
||||
pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let {
|
||||
PollResponseData(
|
||||
myVote = it.aggregatedContent?.myVote,
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.detail.timeline.helper
|
||||
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryData
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
@ActivityScoped
|
||||
class ReactionsSummaryFactory @Inject constructor() {
|
||||
|
||||
var onRequestBuild: (() -> Unit)? = null
|
||||
private val showAllReactionsByEvent = HashSet<String>()
|
||||
private val eventsRequestingBuild = HashSet<String>()
|
||||
|
||||
fun needsRebuild(event: TimelineEvent): Boolean {
|
||||
return eventsRequestingBuild.remove(event.eventId)
|
||||
}
|
||||
|
||||
fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): ReactionsSummaryData {
|
||||
val eventId = event.eventId
|
||||
val showAllStates = showAllReactionsByEvent.contains(eventId)
|
||||
val reactions = event.annotations?.reactionsSummary
|
||||
?.map {
|
||||
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
|
||||
}
|
||||
return ReactionsSummaryData(
|
||||
reactions = reactions,
|
||||
showAll = showAllStates,
|
||||
onShowMoreClicked = {
|
||||
showAllReactionsByEvent.add(eventId)
|
||||
onRequestBuild(eventId)
|
||||
},
|
||||
onShowLessClicked = {
|
||||
showAllReactionsByEvent.remove(eventId)
|
||||
onRequestBuild(eventId)
|
||||
},
|
||||
onAddMoreClicked = {
|
||||
callback?.onAddMoreReaction(event)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun onRequestBuild(eventId: String) {
|
||||
eventsRequestingBuild.add(eventId)
|
||||
onRequestBuild?.invoke()
|
||||
}
|
||||
}
|
|
@ -16,24 +16,34 @@
|
|||
|
||||
package im.vector.app.features.home.room.detail.timeline.item
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Typeface
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.content.ContextCompat.getDrawable
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.getDrawableAsSpannable
|
||||
import im.vector.app.core.ui.views.ShieldImageView
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
|
||||
import im.vector.app.features.reactions.widget.ReactionButton
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
|
||||
private const val MAX_REACTIONS_TO_SHOW = 8
|
||||
|
||||
/**
|
||||
* Base timeline item with reactions and read receipts.
|
||||
* Manages associated click listeners and send status.
|
||||
|
@ -65,27 +75,10 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
|
|||
return listOf(baseAttributes.informationData.eventId)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun bind(holder: H) {
|
||||
super.bind(holder)
|
||||
val reactions = baseAttributes.informationData.orderedReactionList
|
||||
if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) {
|
||||
holder.reactionsContainer.isVisible = false
|
||||
} else {
|
||||
holder.reactionsContainer.isVisible = true
|
||||
holder.reactionsContainer.removeAllViews()
|
||||
reactions.take(8).forEach { reaction ->
|
||||
val reactionButton = ReactionButton(holder.view.context)
|
||||
reactionButton.reactedListener = reactionClickListener
|
||||
reactionButton.setTag(R.id.reactionsContainer, reaction.key)
|
||||
reactionButton.reactionString = reaction.key
|
||||
reactionButton.reactionCount = reaction.count
|
||||
reactionButton.setChecked(reaction.addedByMe)
|
||||
reactionButton.isEnabled = reaction.synced
|
||||
holder.reactionsContainer.addView(reactionButton)
|
||||
}
|
||||
holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener)
|
||||
}
|
||||
|
||||
renderReactions(holder, baseAttributes.informationData.reactionsSummary)
|
||||
when (baseAttributes.informationData.e2eDecoration) {
|
||||
E2EDecoration.NONE -> {
|
||||
holder.e2EDecorationView.render(null)
|
||||
|
@ -102,6 +95,58 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
|
|||
(holder.view as? TimelineMessageLayoutRenderer)?.renderMessageLayout(baseAttributes.informationData.messageLayout)
|
||||
}
|
||||
|
||||
private fun renderReactions(holder: H, reactionsSummary: ReactionsSummaryData) {
|
||||
val reactions = reactionsSummary.reactions
|
||||
if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) {
|
||||
holder.reactionsContainer.isVisible = false
|
||||
} else {
|
||||
holder.reactionsContainer.isVisible = true
|
||||
holder.reactionsContainer.removeAllViews()
|
||||
val reactionsToShow = if (reactionsSummary.showAll) {
|
||||
reactions
|
||||
} else {
|
||||
reactions.take(MAX_REACTIONS_TO_SHOW)
|
||||
}
|
||||
reactionsToShow.forEach { reaction ->
|
||||
val reactionButton = ReactionButton(holder.view.context)
|
||||
reactionButton.reactedListener = reactionClickListener
|
||||
reactionButton.setTag(R.id.reactionsContainer, reaction.key)
|
||||
reactionButton.reactionString = reaction.key
|
||||
reactionButton.reactionCount = reaction.count
|
||||
reactionButton.setChecked(reaction.addedByMe)
|
||||
reactionButton.isEnabled = reaction.synced
|
||||
holder.reactionsContainer.addView(reactionButton)
|
||||
}
|
||||
if (reactions.count() > MAX_REACTIONS_TO_SHOW) {
|
||||
val showReactionsTextView = createReactionTextView(holder)
|
||||
if (reactionsSummary.showAll) {
|
||||
showReactionsTextView.setText(R.string.message_reaction_show_less)
|
||||
showReactionsTextView.onClick { reactionsSummary.onShowLessClicked() }
|
||||
} else {
|
||||
val moreCount = reactions.count() - MAX_REACTIONS_TO_SHOW
|
||||
showReactionsTextView.text = holder.view.resources.getString(R.string.message_reaction_show_more, moreCount)
|
||||
showReactionsTextView.onClick { reactionsSummary.onShowMoreClicked() }
|
||||
}
|
||||
holder.reactionsContainer.addView(showReactionsTextView)
|
||||
}
|
||||
val addMoreReactionsTextView = createReactionTextView(holder)
|
||||
|
||||
addMoreReactionsTextView.text = holder.view.context.getDrawableAsSpannable(R.drawable.ic_add_reaction_small)
|
||||
addMoreReactionsTextView.onClick { reactionsSummary.onAddMoreClicked() }
|
||||
holder.reactionsContainer.addView(addMoreReactionsTextView)
|
||||
holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createReactionTextView(holder: H): TextView {
|
||||
return TextView(ContextThemeWrapper(holder.view.context, R.style.TimelineReactionView)).apply {
|
||||
background = getDrawable(context, R.drawable.reaction_rounded_rect_shape_off)
|
||||
TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Vector_Micro)
|
||||
setTypeface(typeface, Typeface.BOLD)
|
||||
setTextColor(ThemeUtils.getColor(context, R.attr.vctr_content_secondary))
|
||||
}
|
||||
}
|
||||
|
||||
override fun unbind(holder: H) {
|
||||
holder.reactionsContainer.setOnLongClickListener(null)
|
||||
super.unbind(holder)
|
||||
|
@ -115,6 +160,9 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
|
|||
}
|
||||
|
||||
abstract class Holder(@IdRes stubId: Int) : BaseEventItem.BaseHolder(stubId) {
|
||||
val dimensionConverter by lazy {
|
||||
DimensionConverter(view.resources)
|
||||
}
|
||||
val reactionsContainer by bind<ViewGroup>(R.id.reactionsContainer)
|
||||
val e2EDecorationView by bind<ShieldImageView>(R.id.messageE2EDecoration)
|
||||
}
|
||||
|
|
|
@ -33,8 +33,7 @@ data class MessageInformationData(
|
|||
val avatarUrl: String?,
|
||||
val memberName: CharSequence? = null,
|
||||
val messageLayout: TimelineMessageLayout,
|
||||
/*List of reactions (emoji,count,isSelected)*/
|
||||
val orderedReactionList: List<ReactionInfoData>? = null,
|
||||
val reactionsSummary: ReactionsSummaryData,
|
||||
val pollResponseAggregatedSummary: PollResponseData? = null,
|
||||
val hasBeenEdited: Boolean = false,
|
||||
val hasPendingEdits: Boolean = false,
|
||||
|
@ -55,6 +54,16 @@ data class ReferencesInfoData(
|
|||
val verificationStatus: VerificationState
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class ReactionsSummaryData(
|
||||
/*List of reactions (emoji,count,isSelected)*/
|
||||
val reactions: List<ReactionInfoData>? = null,
|
||||
val showAll: Boolean = false,
|
||||
val onShowMoreClicked: () -> Unit,
|
||||
val onShowLessClicked: () -> Unit,
|
||||
val onAddMoreClicked: () -> Unit
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class ReactionInfoData(
|
||||
val key: String,
|
||||
|
|
|
@ -18,7 +18,6 @@ package im.vector.app.features.reactions.widget
|
|||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -26,7 +25,6 @@ import androidx.core.content.withStyledAttributes
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.EmojiSpanify
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.core.utils.TextUtils
|
||||
import im.vector.app.databinding.ReactionButtonBinding
|
||||
import javax.inject.Inject
|
||||
|
@ -38,8 +36,9 @@ import javax.inject.Inject
|
|||
@AndroidEntryPoint
|
||||
class ReactionButton @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0) :
|
||||
LinearLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener {
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = R.style.TimelineReactionView) :
|
||||
LinearLayout(context, attrs, defStyleAttr, defStyleRes), View.OnClickListener, View.OnLongClickListener {
|
||||
|
||||
@Inject lateinit var emojiSpanify: EmojiSpanify
|
||||
|
||||
|
@ -68,8 +67,6 @@ class ReactionButton @JvmOverloads constructor(context: Context,
|
|||
init {
|
||||
inflate(context, R.layout.reaction_button, this)
|
||||
orientation = HORIZONTAL
|
||||
minimumHeight = DimensionConverter(context.resources).dpToPx(30)
|
||||
gravity = Gravity.CENTER
|
||||
layoutDirection = View.LAYOUT_DIRECTION_LOCALE
|
||||
views = ReactionButtonBinding.bind(this)
|
||||
views.reactionCount.text = TextUtils.formatCountToShortDecimal(reactionCount)
|
||||
|
|
4
vector/src/main/res/drawable/ic_add_reaction_small.xml
Normal file
4
vector/src/main/res/drawable/ic_add_reaction_small.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<vector android:height="14dp" android:viewportHeight="16"
|
||||
android:viewportWidth="16" android:width="14dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#737D8C" android:fillType="evenOdd" android:pathData="M13.3334,0.667C12.9652,0.667 12.6667,0.9655 12.6667,1.3337V2.667L11.3334,2.667C10.9652,2.667 10.6667,2.9655 10.6667,3.3337C10.6667,3.7018 10.9652,4.0003 11.3334,4.0003H12.6667V5.3337C12.6667,5.7018 12.9652,6.0003 13.3334,6.0003C13.7016,6.0003 14,5.7018 14,5.3337V4.0003H15.3334C15.7016,4.0003 16,3.7018 16,3.3337C16,2.9655 15.7016,2.667 15.3334,2.667L14,2.667V1.3337C14,0.9655 13.7016,0.667 13.3334,0.667ZM4.6667,6.3337C4.6667,5.7803 5.1134,5.3337 5.6667,5.3337C6.22,5.3337 6.6667,5.7803 6.6667,6.3337C6.6667,6.887 6.22,7.3337 5.6667,7.3337C5.1134,7.3337 4.6667,6.887 4.6667,6.3337ZM10.3334,7.3337C10.8867,7.3337 11.3334,6.887 11.3334,6.3337C11.3334,5.7803 10.8867,5.3337 10.3334,5.3337C9.78,5.3337 9.3334,5.7803 9.3334,6.3337C9.3334,6.887 9.78,7.3337 10.3334,7.3337ZM8,11.667C9.5534,11.667 10.8734,10.6937 11.4067,9.3337H4.5934C5.1267,10.6937 6.4467,11.667 8,11.667ZM2.6667,8.0003C2.6667,5.0548 5.0545,2.667 8,2.667C8.4073,2.667 8.803,2.7125 9.1828,2.7985C9.542,2.8797 9.8989,2.6545 9.9802,2.2954C10.0615,1.9363 9.8362,1.5793 9.4771,1.498C9.0014,1.3903 8.5069,1.3337 8,1.3337C4.3181,1.3337 1.3334,4.3184 1.3334,8.0003C1.3334,11.6822 4.3181,14.667 8,14.667C11.6819,14.667 14.6667,11.6822 14.6667,8.0003C14.6667,7.8589 14.6623,7.7184 14.6536,7.579C14.6306,7.2115 14.3141,6.9322 13.9467,6.9552C13.5792,6.9781 13.2999,7.2946 13.3228,7.6621C13.3298,7.7738 13.3334,7.8866 13.3334,8.0003C13.3334,10.9458 10.9456,13.3337 8,13.3337C5.0545,13.3337 2.6667,10.9458 2.6667,8.0003Z"/>
|
||||
</vector>
|
|
@ -2,7 +2,7 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<size
|
||||
android:width="8dp"
|
||||
android:height="8dp" />
|
||||
android:width="4dp"
|
||||
android:height="4dp" />
|
||||
<solid android:color="#00000000" />
|
||||
</shape>
|
|
@ -165,7 +165,6 @@
|
|||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/roomNameView"
|
||||
app:layout_constraintEnd_toStartOf="@id/roomLastEventTimeView"
|
||||
app:layout_constraintStart_toEndOf="@id/roomDraftBadge"
|
||||
app:layout_constraintTop_toTopOf="@id/roomNameView"
|
||||
tools:background="@drawable/bg_unread_highlight"
|
||||
tools:text="4"
|
||||
|
@ -190,9 +189,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textAlignment="viewStart"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/roomNameView"
|
||||
|
@ -203,12 +202,12 @@
|
|||
android:id="@+id/roomTypingView"
|
||||
style="@style/Widget.Vector.TextView.Body"
|
||||
android:layout_width="0dp"
|
||||
android:textAlignment="viewStart"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?colorPrimary"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="26dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/reaction_rounded_rect_shape"
|
||||
android:clipChildren="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="44dp"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<!--<View-->
|
||||
|
@ -20,12 +19,10 @@
|
|||
android:id="@+id/reactionText"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="center"
|
||||
android:maxEms="10"
|
||||
android:minWidth="20dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/emoji_color"
|
||||
tools:text="* Party Parrot Again * 👀" />
|
||||
|
@ -36,7 +33,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
|
|
|
@ -177,7 +177,7 @@
|
|||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:id="@+id/reactionsContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
app:dividerDrawable="@drawable/reaction_divider"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3779,4 +3779,7 @@
|
|||
<string name="tooltip_attachment_poll">Create poll</string>
|
||||
<string name="tooltip_attachment_location">Share location</string>
|
||||
|
||||
<string name="message_reaction_show_less">Show less</string>
|
||||
<string name="message_reaction_show_more">"%1$d more"</string>
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue