diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt b/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt index 193317a63f..9bd099f2ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt @@ -10,7 +10,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.Guideline import androidx.core.content.withStyledAttributes import androidx.core.graphics.Insets -import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/storage/InternalStorageServicePlaygroundFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/storage/InternalStorageServicePlaygroundFragment.kt index 018f9a343b..8e0ba4255a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/storage/InternalStorageServicePlaygroundFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/storage/InternalStorageServicePlaygroundFragment.kt @@ -285,6 +285,12 @@ private fun StorageRecordRow(record: SignalStorageRecord) { ManifestItemRow("ID", Hex.toStringCondensed(record.id.raw)) } } + record.proto.chatFolder != null -> { + Column { + Text("Chat Folder", fontWeight = FontWeight.Bold) + ManifestItemRow("ID", Hex.toStringCondensed(record.id.raw)) + } + } else -> { Column { Text("Unknown!") 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 d12cb661f6..5d0c1d6e36 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ChatFolderTables.kt @@ -39,6 +39,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress 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 /** @@ -48,6 +49,7 @@ 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) @@ -544,6 +546,36 @@ class ChatFolderTables(context: Context?, databaseHelper: SignalDatabase?) : Dat updateFolder(remoteChatFolderRecordToLocal(record)) } + /** + * Removes storageIds from folders that have been deleted for [DELETED_LIFESPAN]. + */ + 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) + .run() + } + + /** + * Removes storageIds of folders from the given collection because those folders are local only and deleted + */ + fun removeStorageIdsFromLocalOnlyDeletedFolders(storageIds: Collection): Int { + var updated = 0 + + SqlUtil.buildCollectionQuery(ChatFolderTable.STORAGE_SERVICE_ID, storageIds.map { Base64.encodeWithPadding(it.raw) }, "${ChatFolderTable.DELETED_TIMESTAMP_MS} > 0 AND") + .forEach { + updated += writableDatabase.update( + ChatFolderTable.TABLE_NAME, + contentValuesOf(ChatFolderTable.STORAGE_SERVICE_ID to null), + it.where, + it.whereArgs + ) + } + + return updated + } + /** * Maps a chat folder id to all of its corresponding included chats. * If an id is not specified, all chat folder ids will be mapped. diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V270_FixChatFolderColumnsForStorageSync.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V270_FixChatFolderColumnsForStorageSync.kt index af01ffe644..acb48745e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V270_FixChatFolderColumnsForStorageSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V270_FixChatFolderColumnsForStorageSync.kt @@ -1,9 +1,11 @@ package org.thoughtcrime.securesms.database.helpers.migration import android.app.Application +import org.signal.core.util.Base64 import org.signal.core.util.readToList import org.signal.core.util.requireLong import org.thoughtcrime.securesms.database.SQLiteDatabase +import org.thoughtcrime.securesms.storage.StorageSyncHelper import java.util.UUID /** @@ -30,7 +32,7 @@ object V270_FixChatFolderColumnsForStorageSync : SignalDatabaseMigration { db.rawQuery( """ UPDATE chat_folder - SET chat_folder_id = '${UUID.randomUUID()}', position = $newPosition + SET chat_folder_id = '${UUID.randomUUID()}', position = $newPosition, storage_service_id = '${Base64.encodeWithPadding(StorageSyncHelper.generateKey())}' WHERE _id = $id """ ) 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 541ec67a26..219c987685 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt @@ -257,10 +257,11 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param Log.i(TAG, "[Remote Sync] Pre-Merge ID Difference :: $idDifference") if (idDifference.localOnlyIds.isNotEmpty()) { - val updated = SignalDatabase.recipients.removeStorageIdsFromLocalOnlyUnregisteredRecipients(idDifference.localOnlyIds) + val updatedRecipients = SignalDatabase.recipients.removeStorageIdsFromLocalOnlyUnregisteredRecipients(idDifference.localOnlyIds) + val updatedFolders = SignalDatabase.chatFolders.removeStorageIdsFromLocalOnlyDeletedFolders(idDifference.localOnlyIds) - if (updated > 0) { - Log.w(TAG, "Found $updated records that were deleted remotely but only marked unregistered locally. Removed those from local store. Recalculating diff.") + if (updatedRecipients > 0 || updatedFolders > 0) { + Log.w(TAG, "Found $updatedRecipients recipients and $updatedFolders folders that were deleted remotely but only marked unregistered/deleted locally. Removed those from local store. Recalculating diff.") localStorageIdsBeforeMerge = getAllLocalStorageIds(self) idDifference = StorageSyncHelper.findIdDifference(remoteManifest.storageIds, localStorageIdsBeforeMerge) @@ -335,6 +336,11 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param Log.i(TAG, "Removed $removedUnregistered recipients from storage service that have been unregistered for longer than 30 days.") } + val removedDeletedFolders = SignalDatabase.chatFolders.removeStorageIdsFromOldDeletedFolders(System.currentTimeMillis()) + if (removedDeletedFolders > 0) { + Log.i(TAG, "Removed $removedDeletedFolders folders from storage service that have been deleted for longer than 30 days.") + } + val localStorageIds = getAllLocalStorageIds(self) val idDifference = StorageSyncHelper.findIdDifference(remoteManifest.storageIds, localStorageIds) val remoteInserts = buildLocalStorageRecords(context, self, idDifference.localOnlyIds.stream().filter { it: StorageId -> !it.isUnknown }.collect(Collectors.toList()))