From 24f227fd4fa34eab698384a5a6055e25216bc362 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 2 Sep 2024 20:00:27 +0200 Subject: [PATCH] fix crash when there are reblogs in notification statuses (#4638) ``` android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY) at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method) at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:961) at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:790) at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:89) at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.kt:42) at androidx.room.EntityInsertionAdapter.insertAndReturnId(EntityInsertionAdapter.kt:101) at com.keylesspalace.tusky.db.dao.TimelineStatusDao_Impl$insert$2.call(TimelineStatusDao_Impl.kt:345) at com.keylesspalace.tusky.db.dao.TimelineStatusDao_Impl$insert$2.call(TimelineStatusDao_Impl.kt:340) at androidx.room.CoroutinesRoom$Companion.execute(CoroutinesRoom.kt:56) at com.keylesspalace.tusky.db.dao.TimelineStatusDao_Impl.insert(TimelineStatusDao_Impl.kt:340) at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator.replaceNotificationRange(NotificationsRemoteMediator.kt:169) at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator.access$replaceNotificationRange(NotificationsRemoteMediator.kt:36) at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator$load$3.invokeSuspend(NotificationsRemoteMediator.kt:109) at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator$load$3.invoke(Unknown Source:8) at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator$load$3.invoke(Unknown Source:2) at androidx.room.RoomDatabaseKt$withTransaction$transactionBlock$1.invokeSuspend(RoomDatabaseExt.kt:62) at androidx.room.RoomDatabaseKt$withTransaction$transactionBlock$1.invoke(Unknown Source:8) at androidx.room.RoomDatabaseKt$withTransaction$transactionBlock$1.invoke(Unknown Source:4) at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:61) at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:163) at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1) at androidx.room.RoomDatabaseKt$startTransactionCoroutine$2$1$1.invokeSuspend(RoomDatabaseExt.kt:103) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104) at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277) at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:95) at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:69) at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source:1) at androidx.room.RoomDatabaseKt$startTransactionCoroutine$2$1.run(RoomDatabaseExt.kt:99) at androidx.room.TransactionExecutor.execute$lambda$1$lambda$0(TransactionExecutor.kt:36) at androidx.room.TransactionExecutor.$r8$lambda$FZWr2PGmP3sgXLCiri-DCcePXSs(Unknown Source:0) at androidx.room.TransactionExecutor$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) at java.lang.Thread.run(Thread.java:1012) ``` It looks kinda weird because "x just posted" has a different user than the actual post, but it works for groups I guess? And definitely better than crashing. closes #4563 --- .../notifications/NotificationTypeMappers.kt | 2 +- .../NotificationsRemoteMediator.kt | 6 +++--- .../notifications/NotificationsViewModel.kt | 5 +++-- .../NotificationsRemoteMediatorTest.kt | 21 +++++++++++++++++-- .../components/timeline/TimelineFaker.kt | 5 +++-- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationTypeMappers.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationTypeMappers.kt index d3248a672..0ad4deffc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationTypeMappers.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationTypeMappers.kt @@ -47,7 +47,7 @@ fun Notification.toEntity( type = type, id = id, accountId = account.id, - statusId = status?.id, + statusId = status?.reblog?.id ?: status?.id, reportId = report?.id, loading = false ) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediator.kt index d6627da58..d3b71f750 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediator.kt @@ -164,10 +164,10 @@ class NotificationsRemoteMediator( val contentShowing = oldStatus?.contentShowing ?: (activeAccount.alwaysShowSensitiveMedia || !status.sensitive) val contentCollapsed = oldStatus?.contentCollapsed ?: true - accountDao.insert(status.account.toEntity(activeAccount.id)) - + val statusToInsert = status.reblog ?: status + accountDao.insert(statusToInsert.account.toEntity(activeAccount.id)) statusDao.insert( - status.toEntity( + statusToInsert.toEntity( tuskyAccountId = activeAccount.id, expanded = expanded, contentShowing = contentShowing, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt index 3777b84fe..5e74003ec 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt @@ -345,10 +345,11 @@ class NotificationsViewModel @Inject constructor( notificationsDao.insertReport(report.toEntity(account.id)) } notification.status?.let { status -> - accountDao.insert(status.account.toEntity(account.id)) + val statusToInsert = status.reblog ?: status + accountDao.insert(statusToInsert.account.toEntity(account.id)) statusDao.insert( - status.toEntity( + statusToInsert.toEntity( tuskyAccountId = account.id, expanded = account.alwaysOpenSpoiler, contentShowing = account.alwaysShowSensitiveMedia || !status.sensitive, diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediatorTest.kt index 0f2c9abb4..34ae432bb 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediatorTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediatorTest.kt @@ -11,6 +11,7 @@ import androidx.room.Room import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.keylesspalace.tusky.components.timeline.Placeholder +import com.keylesspalace.tusky.components.timeline.fakeStatus import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.Converters @@ -215,7 +216,17 @@ class NotificationsRemoteMediatorTest { api = mock { onBlocking { notifications(limit = 20, excludes = emptySet()) } doReturn Response.success( listOf( - fakeNotification(id = "8"), + // testing for https://github.com/tuskyapp/Tusky/issues/4563 + fakeNotification( + id = "8", + status = fakeStatus( + id = "r1", + reblog = fakeStatus( + id = "8", + authorServerId = "r1" + ) + ) + ), fakeNotification(id = "7"), fakeNotification(id = "5") ) @@ -249,7 +260,13 @@ class NotificationsRemoteMediatorTest { db.assertNotifications( listOf( - fakeNotification(id = "8").toNotificationDataEntity(1), + fakeNotification( + id = "8", + status = fakeStatus( + id = "8", + authorServerId = "r1" + ) + ).toNotificationDataEntity(1), fakeNotification(id = "7").toNotificationDataEntity(1), fakeNotification(id = "5").toNotificationDataEntity(1), fakeNotification(id = "3").toNotificationDataEntity(1), diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineFaker.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineFaker.kt index 2351347dc..80fc0c25d 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineFaker.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineFaker.kt @@ -35,7 +35,8 @@ fun fakeStatus( reblogged: Boolean = false, favourited: Boolean = true, bookmarked: Boolean = true, - domain: String = "mastodon.example" + domain: String = "mastodon.example", + reblog: Status? = null ) = Status( id = id, url = "https://$domain/@ConnyDuck/$id", @@ -45,7 +46,7 @@ fun fakeStatus( ), inReplyToId = inReplyToId, inReplyToAccountId = inReplyToAccountId, - reblog = null, + reblog = reblog, content = "Test", createdAt = fixedDate, editedAt = null,