Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Create StatusDao for operations that act on individual statuses #1252

Merged
merged 1 commit into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions app/src/main/java/app/pachli/appstore/CacheUpdater.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package app.pachli.appstore

import app.pachli.core.data.repository.AccountManager
import app.pachli.core.database.dao.StatusDao
import app.pachli.core.database.dao.TimelineDao
import app.pachli.core.eventhub.BookmarkEvent
import app.pachli.core.eventhub.EventHub
Expand All @@ -21,6 +22,7 @@ class CacheUpdater @Inject constructor(
eventHub: EventHub,
accountManager: AccountManager,
timelineDao: TimelineDao,
statusDao: StatusDao,
) {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

Expand All @@ -30,20 +32,20 @@ class CacheUpdater @Inject constructor(
val accountId = accountManager.activeAccount?.id ?: return@collect
when (event) {
is FavoriteEvent ->
timelineDao.setFavourited(accountId, event.statusId, event.favourite)
statusDao.setFavourited(accountId, event.statusId, event.favourite)
is ReblogEvent ->
timelineDao.setReblogged(accountId, event.statusId, event.reblog)
statusDao.setReblogged(accountId, event.statusId, event.reblog)
is BookmarkEvent ->
timelineDao.setBookmarked(accountId, event.statusId, event.bookmark)
statusDao.setBookmarked(accountId, event.statusId, event.bookmark)
is UnfollowEvent ->
timelineDao.removeAllByUser(accountId, event.accountId)
is StatusDeletedEvent ->
timelineDao.delete(accountId, event.statusId)
statusDao.delete(accountId, event.statusId)
is PollVoteEvent -> {
timelineDao.setVoted(accountId, event.statusId, event.poll)
statusDao.setVoted(accountId, event.statusId, event.poll)
}
is PinEvent ->
timelineDao.setPinned(accountId, event.statusId, event.pinned)
statusDao.setPinned(accountId, event.statusId, event.pinned)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import app.pachli.components.timeline.viewmodel.CachedTimelineRemoteMediator.Com
import app.pachli.core.common.di.ApplicationScope
import app.pachli.core.data.model.StatusViewData
import app.pachli.core.database.dao.RemoteKeyDao
import app.pachli.core.database.dao.StatusDao
import app.pachli.core.database.dao.TimelineDao
import app.pachli.core.database.dao.TranslatedStatusDao
import app.pachli.core.database.di.TransactionProvider
Expand Down Expand Up @@ -66,6 +67,7 @@ class CachedTimelineRepository @Inject constructor(
val timelineDao: TimelineDao,
private val remoteKeyDao: RemoteKeyDao,
private val translatedStatusDao: TranslatedStatusDao,
private val statusDao: StatusDao,
@ApplicationScope private val externalScope: CoroutineScope,
) : TimelineRepository<TimelineStatusWithAccount> {
private var factory: InvalidatingPagingSourceFactory<Int, TimelineStatusWithAccount>? = null
Expand Down Expand Up @@ -97,6 +99,7 @@ class CachedTimelineRepository @Inject constructor(
transactionProvider,
timelineDao,
remoteKeyDao,
statusDao,
),
pagingSourceFactory = factory!!,
).flow
Expand Down Expand Up @@ -152,7 +155,7 @@ class CachedTimelineRepository @Inject constructor(

/** Clear the warning (remove the "filtered" setting) for the given status, for the active account */
suspend fun clearStatusWarning(pachliAccountId: Long, statusId: String) = externalScope.launch {
timelineDao.clearWarning(pachliAccountId, statusId)
statusDao.clearWarning(pachliAccountId, statusId)
}.join()

suspend fun translate(statusViewData: StatusViewData): NetworkResult<Translation> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import app.pachli.core.database.dao.RemoteKeyDao
import app.pachli.core.database.dao.StatusDao
import app.pachli.core.database.dao.TimelineDao
import app.pachli.core.database.di.TransactionProvider
import app.pachli.core.database.model.RemoteKeyEntity
Expand All @@ -48,6 +49,7 @@ class CachedTimelineRemoteMediator(
private val transactionProvider: TransactionProvider,
private val timelineDao: TimelineDao,
private val remoteKeyDao: RemoteKeyDao,
private val statusDao: StatusDao,
) : RemoteMediator<Int, TimelineStatusWithAccount>() {
override suspend fun load(
loadType: LoadType,
Expand Down Expand Up @@ -259,7 +261,7 @@ class CachedTimelineRemoteMediator(
}

timelineDao.upsertAccounts(accounts.map { TimelineAccountEntity.from(it, pachliAccountId) })
timelineDao.upsertStatuses(statuses.map { StatusEntity.from(it, pachliAccountId) })
statusDao.upsertStatuses(statuses.map { StatusEntity.from(it, pachliAccountId) })
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class CachedTimelineRemoteMediatorTest {
transactionProvider = transactionProvider,
timelineDao = db.timelineDao(),
remoteKeyDao = db.remoteKeyDao(),
statusDao = db.statusDao(),
)

val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state()) }
Expand All @@ -123,6 +124,7 @@ class CachedTimelineRemoteMediatorTest {
transactionProvider = transactionProvider,
timelineDao = db.timelineDao(),
remoteKeyDao = db.remoteKeyDao(),
statusDao = db.statusDao(),
)

val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state()) }
Expand All @@ -140,6 +142,7 @@ class CachedTimelineRemoteMediatorTest {
transactionProvider = transactionProvider,
timelineDao = db.timelineDao(),
remoteKeyDao = db.remoteKeyDao(),
statusDao = db.statusDao(),
)

val state = state(
Expand Down Expand Up @@ -177,6 +180,7 @@ class CachedTimelineRemoteMediatorTest {
transactionProvider = transactionProvider,
timelineDao = db.timelineDao(),
remoteKeyDao = db.remoteKeyDao(),
statusDao = db.statusDao(),
)

val state = state(
Expand Down Expand Up @@ -228,6 +232,7 @@ class CachedTimelineRemoteMediatorTest {
transactionProvider = transactionProvider,
timelineDao = db.timelineDao(),
remoteKeyDao = db.remoteKeyDao(),
statusDao = db.statusDao(),
)

val state = state(
Expand Down Expand Up @@ -286,6 +291,7 @@ class CachedTimelineRemoteMediatorTest {
transactionProvider = transactionProvider,
timelineDao = db.timelineDao(),
remoteKeyDao = db.remoteKeyDao(),
statusDao = db.statusDao(),
)

val state = state(
Expand Down Expand Up @@ -335,7 +341,7 @@ class CachedTimelineRemoteMediatorTest {
statusWithAccount.reblogAccount?.let { account ->
timelineDao().insertAccount(account)
}
timelineDao().insertStatus(statusWithAccount.status)
statusDao().insertStatus(statusWithAccount.status)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.paging.RemoteMediator
import app.pachli.core.data.repository.notifications.NotificationsRepository.Companion.RKE_TIMELINE_ID
import app.pachli.core.database.dao.NotificationDao
import app.pachli.core.database.dao.RemoteKeyDao
import app.pachli.core.database.dao.StatusDao
import app.pachli.core.database.dao.TimelineDao
import app.pachli.core.database.di.TransactionProvider
import app.pachli.core.database.model.NotificationData
Expand Down Expand Up @@ -59,6 +60,7 @@ class NotificationsRemoteMediator(
private val timelineDao: TimelineDao,
private val remoteKeyDao: RemoteKeyDao,
private val notificationDao: NotificationDao,
private val statusDao: StatusDao,
) : RemoteMediator<Int, NotificationData>() {

override suspend fun load(loadType: LoadType, state: PagingState<Int, NotificationData>): MediatorResult {
Expand Down Expand Up @@ -282,7 +284,7 @@ class NotificationsRemoteMediator(

// Bulk upsert the discovered items.
timelineDao.upsertAccounts(accounts.map { TimelineAccountEntity.from(it, pachliAccountId) })
timelineDao.upsertStatuses(statuses.map { StatusEntity.from(it, pachliAccountId) })
statusDao.upsertStatuses(statuses.map { StatusEntity.from(it, pachliAccountId) })
notificationDao.upsertReports(reports.mapNotNull { NotificationReportEntity.from(pachliAccountId, it) })
notificationDao.upsertEvents(
severanceEvents.mapNotNull {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import app.pachli.core.common.di.ApplicationScope
import app.pachli.core.data.model.StatusViewData
import app.pachli.core.database.dao.NotificationDao
import app.pachli.core.database.dao.RemoteKeyDao
import app.pachli.core.database.dao.StatusDao
import app.pachli.core.database.dao.TimelineDao
import app.pachli.core.database.di.TransactionProvider
import app.pachli.core.database.model.AccountFilterDecisionUpdate
Expand Down Expand Up @@ -97,6 +98,7 @@ class NotificationsRepository @Inject constructor(
private val timelineDao: TimelineDao,
private val notificationDao: NotificationDao,
private val remoteKeyDao: RemoteKeyDao,
private val statusDao: StatusDao,
private val eventHub: EventHub,
) {
private var factory: InvalidatingPagingSourceFactory<Int, NotificationData>? = null
Expand Down Expand Up @@ -126,6 +128,7 @@ class NotificationsRepository @Inject constructor(
timelineDao,
remoteKeyDao,
notificationDao,
statusDao,
),
pagingSourceFactory = factory!!,
).flow
Expand Down Expand Up @@ -262,12 +265,12 @@ class NotificationsRepository @Inject constructor(
}
}

timelineDao.setBookmarked(pachliAccountId, statusId, bookmarked)
statusDao.setBookmarked(pachliAccountId, statusId, bookmarked)

val result = deferred.await()

result.onFailure { throwable ->
timelineDao.setBookmarked(pachliAccountId, statusId, !bookmarked)
statusDao.setBookmarked(pachliAccountId, statusId, !bookmarked)
return@async Err(StatusActionError.Bookmark(throwable))
}

Expand Down Expand Up @@ -298,12 +301,12 @@ class NotificationsRepository @Inject constructor(
}
}

timelineDao.setFavourited(pachliAccountId, statusId, favourited)
statusDao.setFavourited(pachliAccountId, statusId, favourited)

val result = deferred.await()

result.onFailure { throwable ->
timelineDao.setFavourited(pachliAccountId, statusId, !favourited)
statusDao.setFavourited(pachliAccountId, statusId, !favourited)
return@async Err(StatusActionError.Favourite(throwable))
}

Expand Down Expand Up @@ -334,12 +337,12 @@ class NotificationsRepository @Inject constructor(
}
}

timelineDao.setReblogged(pachliAccountId, statusId, reblogged)
statusDao.setReblogged(pachliAccountId, statusId, reblogged)

val result = deferred.await()

result.onFailure { throwable ->
timelineDao.setReblogged(pachliAccountId, statusId, !reblogged)
statusDao.setReblogged(pachliAccountId, statusId, !reblogged)
return@async Err(StatusActionError.Reblog(throwable))
}

Expand Down Expand Up @@ -370,7 +373,7 @@ class NotificationsRepository @Inject constructor(

mastodonApi.voteInPoll(pollId, choices)
.onSuccess { poll ->
timelineDao.setVoted(pachliAccountId, statusId, poll)
statusDao.setVoted(pachliAccountId, statusId, poll)
eventHub.dispatch(PollVoteEvent(statusId, poll))
}
.onFailure { throwable -> return@async Err(StatusActionError.VoteInPoll(throwable)) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import app.pachli.core.database.dao.ListsDao
import app.pachli.core.database.dao.LogEntryDao
import app.pachli.core.database.dao.NotificationDao
import app.pachli.core.database.dao.RemoteKeyDao
import app.pachli.core.database.dao.StatusDao
import app.pachli.core.database.dao.TimelineDao
import app.pachli.core.database.dao.TranslatedStatusDao
import app.pachli.core.database.model.AccountEntity
Expand Down Expand Up @@ -125,6 +126,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun announcementsDao(): AnnouncementsDao
abstract fun followingAccountDao(): FollowingAccountDao
abstract fun notificationDao(): NotificationDao
abstract fun statusDao(): StatusDao

@DeleteColumn("TimelineStatusEntity", "expanded")
@DeleteColumn("TimelineStatusEntity", "contentCollapsed")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright (c) 2025 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/

package app.pachli.core.database.dao

import androidx.room.Dao
import androidx.room.Query
import androidx.room.TypeConverters
import androidx.room.Upsert
import app.pachli.core.database.Converters
import app.pachli.core.database.model.StatusEntity
import app.pachli.core.network.model.Poll

/**
* Operations on individual statuses, irrespective of the timeline they are
* part of.
*/
@Dao
@TypeConverters(Converters::class)
abstract class StatusDao {
@Upsert
abstract suspend fun upsertStatuses(statuses: Collection<StatusEntity>)

@Upsert
abstract suspend fun insertStatus(statusEntity: StatusEntity): Long

@Query(
"""
UPDATE StatusEntity
SET
favourited = :favourited
WHERE timelineUserId = :pachliAccountId AND (serverId = :statusId OR reblogServerId = :statusId)
""",
)
abstract suspend fun setFavourited(pachliAccountId: Long, statusId: String, favourited: Boolean)

@Query(
"""
UPDATE StatusEntity
SET
bookmarked = :bookmarked
WHERE timelineUserId = :pachliAccountId AND (serverId = :statusId OR reblogServerId = :statusId)
""",
)
abstract suspend fun setBookmarked(pachliAccountId: Long, statusId: String, bookmarked: Boolean)

@Query(
"""
UPDATE StatusEntity
SET
reblogged = :reblogged
WHERE timelineUserId = :pachliAccountId AND (serverId = :statusId OR reblogServerId = :statusId)
""",
)
abstract suspend fun setReblogged(pachliAccountId: Long, statusId: String, reblogged: Boolean)

@Query(
"""
DELETE
FROM StatusEntity
WHERE
timelineUserId = :accountId
AND serverId = :statusId
""",
)
abstract suspend fun delete(accountId: Long, statusId: String)

@Query(
"""
UPDATE StatusEntity
SET
poll = :poll
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)
""",
)
abstract suspend fun setVoted(accountId: Long, statusId: String, poll: Poll)

@Query(
"""
UPDATE StatusEntity
SET
pinned = :pinned
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)
""",
)
abstract suspend fun setPinned(accountId: Long, statusId: String, pinned: Boolean)

@Query(
"""
UPDATE StatusEntity
SET
filtered = NULL
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)
""",
)
abstract suspend fun clearWarning(accountId: Long, statusId: String): Int
}
Loading