diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt index a2d00d63d2..788f5a8493 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt @@ -30,10 +30,10 @@ import org.thoughtcrime.securesms.database.ThreadTable.Companion.ID import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.storage.StorageSyncModels +import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.storage.SignalChatFolderRecord import org.whispersystems.signalservice.api.storage.StorageId import org.whispersystems.signalservice.api.util.UuidUtil -import java.util.concurrent.TimeUnit import org.whispersystems.signalservice.internal.storage.protos.ChatFolderRecord as RemoteChatFolderRecord /** @@ -43,7 +43,6 @@ class ChatFolderTables(context: Context?, databaseHelper: SignalDatabase?) : Dat companion object { private val TAG = Log.tag(ChatFolderTable::class.java) - private val DELETED_LIFESPAN: Long = TimeUnit.DAYS.toMillis(30) @JvmField val CREATE_TABLE: Array = arrayOf(ChatFolderTable.CREATE_TABLE, ChatFolderMembershipTable.CREATE_TABLE) @@ -543,13 +542,13 @@ class ChatFolderTables(context: Context?, databaseHelper: SignalDatabase?) : Dat } /** - * Removes storageIds from folders that have been deleted for [DELETED_LIFESPAN]. + * Removes storageIds from folders that have been deleted for [RemoteConfig.messageQueueTime]. */ fun removeStorageIdsFromOldDeletedFolders(now: Long): Int { return writableDatabase .update(ChatFolderTable.TABLE_NAME) .values(ChatFolderTable.STORAGE_SERVICE_ID to null) - .where("${ChatFolderTable.STORAGE_SERVICE_ID} NOT NULL AND ${ChatFolderTable.DELETED_TIMESTAMP_MS} > 0 AND ${ChatFolderTable.DELETED_TIMESTAMP_MS} < ?", now - DELETED_LIFESPAN) + .where("${ChatFolderTable.STORAGE_SERVICE_ID} NOT NULL AND ${ChatFolderTable.DELETED_TIMESTAMP_MS} > 0 AND ${ChatFolderTable.DELETED_TIMESTAMP_MS} < ?", now - RemoteConfig.messageQueueTime) .run() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/NotificationProfileTables.kt b/app/src/main/java/org/thoughtcrime/securesms/database/NotificationProfileTables.kt index 8f8a06af62..08f45dbb45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/NotificationProfileTables.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/NotificationProfileTables.kt @@ -34,11 +34,11 @@ import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.storage.StorageSyncModels import org.thoughtcrime.securesms.storage.StorageSyncModels.toLocal +import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.storage.SignalNotificationProfileRecord import org.whispersystems.signalservice.api.storage.StorageId import org.whispersystems.signalservice.api.util.UuidUtil import java.time.DayOfWeek -import kotlin.time.Duration.Companion.days /** * Database for maintaining Notification Profiles, Notification Profile Schedules, and Notification Profile allowed memebers. @@ -47,7 +47,6 @@ class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase companion object { private val TAG = Log.tag(NotificationProfileTable::class) - private val DELETED_LIFESPAN: Long = 30.days.inWholeMilliseconds @JvmField val CREATE_TABLE: Array = arrayOf(NotificationProfileTable.CREATE_TABLE, NotificationProfileScheduleTable.CREATE_TABLE, NotificationProfileAllowedMembersTable.CREATE_TABLE) @@ -496,13 +495,13 @@ class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase } /** - * Removes storageIds from notification profiles that have been deleted for [DELETED_LIFESPAN]. + * Removes storageIds from notification profiles that have been deleted for [RemoteConfig.messageQueueTime]. */ fun removeStorageIdsFromOldDeletedProfiles(now: Long): Int { return writableDatabase .update(NotificationProfileTable.TABLE_NAME) .values(NotificationProfileTable.STORAGE_SERVICE_ID to null) - .where("${NotificationProfileTable.STORAGE_SERVICE_ID} NOT NULL AND ${NotificationProfileTable.DELETED_TIMESTAMP_MS} > 0 AND ${NotificationProfileTable.DELETED_TIMESTAMP_MS} < ?", now - DELETED_LIFESPAN) + .where("${NotificationProfileTable.STORAGE_SERVICE_ID} NOT NULL AND ${NotificationProfileTable.DELETED_TIMESTAMP_MS} > 0 AND ${NotificationProfileTable.DELETED_TIMESTAMP_MS} < ?", now - RemoteConfig.messageQueueTime) .run() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index d5d30374b2..f39f6b72b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -120,7 +120,6 @@ import java.io.IOException import java.util.Collections import java.util.LinkedList import java.util.Optional -import java.util.concurrent.TimeUnit import kotlin.jvm.optionals.getOrNull import kotlin.math.max @@ -129,8 +128,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val TAG = Log.tag(RecipientTable::class.java) companion object { - private val UNREGISTERED_LIFESPAN: Long = TimeUnit.DAYS.toMillis(30) - const val TABLE_NAME = "recipient" const val ID = "_id" @@ -1091,14 +1088,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } /** - * Removes storageIds from unregistered recipients who were unregistered more than [UNREGISTERED_LIFESPAN] ago. + * Removes storageIds from unregistered recipients who were unregistered more than [RemoteConfig.messageQueueTime] ago. * @return The number of rows affected. */ fun removeStorageIdsFromOldUnregisteredRecipients(now: Long): Int { return writableDatabase .update(TABLE_NAME) .values(STORAGE_SERVICE_ID to null) - .where("$STORAGE_SERVICE_ID NOT NULL AND $UNREGISTERED_TIMESTAMP > 0 AND $UNREGISTERED_TIMESTAMP < ?", now - UNREGISTERED_LIFESPAN) + .where("$STORAGE_SERVICE_ID NOT NULL AND $UNREGISTERED_TIMESTAMP > 0 AND $UNREGISTERED_TIMESTAMP < ?", now - RemoteConfig.messageQueueTime) .run() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt index 64c7746096..dfa45e5e8c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt @@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncModels import org.thoughtcrime.securesms.storage.StorageSyncValidations import org.thoughtcrime.securesms.storage.StoryDistributionListRecordProcessor import org.thoughtcrime.securesms.transport.RetryLaterException +import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage @@ -63,6 +64,7 @@ import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord import java.io.IOException import java.util.concurrent.TimeUnit import java.util.stream.Collectors +import kotlin.time.Duration.Companion.milliseconds /** * Does a full sync of our local storage state with the remote storage state. Will write any pending @@ -360,7 +362,7 @@ class StorageSyncJob private constructor(parameters: Parameters, private var loc val removedDeletedFolders = SignalDatabase.chatFolders.removeStorageIdsFromOldDeletedFolders(System.currentTimeMillis()) val removedDeletedProfiles = SignalDatabase.notificationProfiles.removeStorageIdsFromOldDeletedProfiles(System.currentTimeMillis()) if (removedUnregistered > 0 || removedDeletedFolders > 0 || removedDeletedProfiles > 0) { - Log.i(TAG, "Removed $removedUnregistered unregistered, $removedDeletedFolders folders, $removedDeletedProfiles notification profiles from storage service that have been deleted for longer than 30 days.") + Log.i(TAG, "Removed $removedUnregistered unregistered, $removedDeletedFolders folders, $removedDeletedProfiles notification profiles from storage service that have been deleted for longer than ${RemoteConfig.messageQueueTime.milliseconds.inWholeDays} days.") } val localStorageIds = getAllLocalStorageIds(self) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt index 7c1a501aef..f6aedb6ffd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt @@ -1114,5 +1114,16 @@ object RemoteConfig { hotSwappable = true ) + /** + * Also determines how long an unregistered/deleted record should remain in storage service + */ + val messageQueueTime: Long by remoteValue( + key = "global.messageQueueTimeInSeconds", + hotSwappable = true + ) { value -> + val inSeconds = value.asLong(45.days.inWholeSeconds) + inSeconds.seconds.inWholeMilliseconds + } + // endregion } diff --git a/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.kt b/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.kt index ac8d56faf3..e22559a009 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.kt @@ -1,24 +1,43 @@ package org.thoughtcrime.securesms.storage +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.unmockkObject import okio.ByteString +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test import org.thoughtcrime.securesms.storage.StorageSyncHelper.findIdDifference import org.thoughtcrime.securesms.storage.StorageSyncHelper.profileKeyChanged import org.thoughtcrime.securesms.testutil.TestHelpers +import org.thoughtcrime.securesms.util.RemoteConfig import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI.Companion.parseOrThrow import org.whispersystems.signalservice.api.storage.SignalContactRecord import org.whispersystems.signalservice.api.storage.SignalRecord import org.whispersystems.signalservice.api.storage.StorageId import org.whispersystems.signalservice.internal.storage.protos.ContactRecord +import kotlin.time.Duration.Companion.days class StorageSyncHelperTest { + @Before + fun setup() { + mockkObject(RemoteConfig) + } + + @After + fun tearDown() { + unmockkObject(RemoteConfig) + } + @Test fun findIdDifference_allOverlap() { + every { RemoteConfig.messageQueueTime } returns 45.days.inWholeMilliseconds + val result = findIdDifference(keyListOf(1, 2, 3), keyListOf(1, 2, 3)) assertTrue(result.localOnlyIds.isEmpty()) assertTrue(result.remoteOnlyIds.isEmpty()) @@ -27,6 +46,8 @@ class StorageSyncHelperTest { @Test fun findIdDifference_noOverlap() { + every { RemoteConfig.messageQueueTime } returns 45.days.inWholeMilliseconds + val result = findIdDifference(keyListOf(1, 2, 3), keyListOf(4, 5, 6)) TestHelpers.assertContentsEqual(keyListOf(1, 2, 3), result.remoteOnlyIds) TestHelpers.assertContentsEqual(keyListOf(4, 5, 6), result.localOnlyIds) @@ -35,6 +56,8 @@ class StorageSyncHelperTest { @Test fun findIdDifference_someOverlap() { + every { RemoteConfig.messageQueueTime } returns 45.days.inWholeMilliseconds + val result = findIdDifference(keyListOf(1, 2, 3), keyListOf(2, 3, 4)) TestHelpers.assertContentsEqual(keyListOf(1), result.remoteOnlyIds) TestHelpers.assertContentsEqual(keyListOf(4), result.localOnlyIds) @@ -43,6 +66,8 @@ class StorageSyncHelperTest { @Test fun findIdDifference_typeMismatch_allOverlap() { + every { RemoteConfig.messageQueueTime } returns 45.days.inWholeMilliseconds + val result = findIdDifference( keyListOf( mapOf( @@ -65,6 +90,8 @@ class StorageSyncHelperTest { @Test fun findIdDifference_typeMismatch_someOverlap() { + every { RemoteConfig.messageQueueTime } returns 45.days.inWholeMilliseconds + val result = findIdDifference( keyListOf( mapOf( @@ -89,6 +116,8 @@ class StorageSyncHelperTest { @Test fun test_ContactUpdate_equals_sameProfileKeys() { + every { RemoteConfig.messageQueueTime } returns 45.days.inWholeMilliseconds + val profileKey = ByteArray(32) val profileKeyCopy = profileKey.clone() @@ -106,6 +135,8 @@ class StorageSyncHelperTest { @Test fun test_ContactUpdate_equals_differentProfileKeys() { + every { RemoteConfig.messageQueueTime } returns 45.days.inWholeMilliseconds + val profileKey = ByteArray(32) val profileKeyCopy = profileKey.clone() profileKeyCopy[0] = 1