Skip to content
This repository has been archived by the owner on Sep 23, 2024. It is now read-only.

Commit

Permalink
Account posts paging refactor (#563)
Browse files Browse the repository at this point in the history
- moving to the new paging methods for account timelines

---------

Co-authored-by: John Oberhauser <j.git-global@obez.io>
  • Loading branch information
JohnOberhauser and John Oberhauser authored May 28, 2024
1 parent df37ca7 commit b11b10a
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 229 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,25 @@ interface AccountApi {

suspend fun getAccountFollowers(
accountId: String,
olderThanId: String? = null,
newerThanId: String? = null,
immediatelyNewerThanId: String? = null,
maxId: String? = null,
sinceId: String? = null,
minId: String? = null,
limit: Int? = null,
): Response<List<NetworkAccount>>

suspend fun getAccountFollowing(
accountId: String,
olderThanId: String? = null,
newerThanId: String? = null,
immediatelyNewerThanId: String? = null,
maxId: String? = null,
sinceId: String? = null,
minId: String? = null,
limit: Int? = null,
): Response<List<NetworkAccount>>

suspend fun getAccountStatuses(
accountId: String,
olderThanId: String? = null,
newerThanId: String? = null,
immediatelyNewerThanId: String? = null,
maxId: String? = null,
sinceId: String? = null,
minId: String? = null,
limit: Int? = null,
onlyMedia: Boolean = false,
excludeReplies: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@ package social.firefly.core.network.mastodon.ktor

import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.request.forms.FormDataContent
import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
import io.ktor.client.request.patch
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.URLProtocol
import io.ktor.http.parameters
import io.ktor.http.path
Expand Down Expand Up @@ -49,47 +46,47 @@ class AccountApiImpl(

override suspend fun getAccountFollowers(
accountId: String,
olderThanId: String?,
newerThanId: String?,
immediatelyNewerThanId: String?,
maxId: String?,
sinceId: String?,
minId: String?,
limit: Int?
): Response<List<NetworkAccount>> = client.get {
url { _ ->
protocol = URLProtocol.HTTPS
path("api/v1/accounts/$accountId/followers")
parameters.apply {
olderThanId?.let { append("max_id", it) }
newerThanId?.let { append("since_id", it) }
immediatelyNewerThanId?.let { append("min_id", it) }
maxId?.let { append("max_id", it) }
sinceId?.let { append("since_id", it) }
minId?.let { append("min_id", it) }
limit?.let { append("limit", it.toString()) }
}
}
}.toResponse()

override suspend fun getAccountFollowing(
accountId: String,
olderThanId: String?,
newerThanId: String?,
immediatelyNewerThanId: String?,
maxId: String?,
sinceId: String?,
minId: String?,
limit: Int?
): Response<List<NetworkAccount>> = client.get {
url { _ ->
protocol = URLProtocol.HTTPS
path("api/v1/accounts/$accountId/following")
parameters.apply {
olderThanId?.let { append("max_id", it) }
newerThanId?.let { append("since_id", it) }
immediatelyNewerThanId?.let { append("min_id", it) }
maxId?.let { append("max_id", it) }
sinceId?.let { append("since_id", it) }
minId?.let { append("min_id", it) }
limit?.let { append("limit", it.toString()) }
}
}
}.toResponse()

override suspend fun getAccountStatuses(
accountId: String,
olderThanId: String?,
newerThanId: String?,
immediatelyNewerThanId: String?,
maxId: String?,
sinceId: String?,
minId: String?,
limit: Int?,
onlyMedia: Boolean,
excludeReplies: Boolean,
Expand All @@ -99,9 +96,9 @@ class AccountApiImpl(
protocol = URLProtocol.HTTPS
path("api/v1/accounts/$accountId/statuses")
parameters.apply {
olderThanId?.let { append("max_id", it) }
newerThanId?.let { append("since_id", it) }
immediatelyNewerThanId?.let { append("min_id", it) }
maxId?.let { append("max_id", it) }
sinceId?.let { append("since_id", it) }
minId?.let { append("min_id", it) }
limit?.let { append("limit", it.toString()) }
append("only_media", onlyMedia.toString())
append("exclude_replies", excludeReplies.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,40 @@ class AccountRepository internal constructor(

suspend fun getAccountFollowers(
accountId: String,
olderThanId: String? = null,
newerThanId: String? = null,
maxId: String? = null,
sinceId: String? = null,
loadSize: Int? = null,
): MastodonPagedResponse<Account> = api.getAccountFollowers(
accountId = accountId,
olderThanId = olderThanId,
newerThanId = newerThanId,
maxId = maxId,
sinceId = sinceId,
limit = loadSize,
).toMastodonPagedResponse { it.toExternalModel() }

suspend fun getAccountFollowing(
accountId: String,
olderThanId: String? = null,
newerThanId: String? = null,
maxId: String? = null,
sinceId: String? = null,
loadSize: Int? = null,
): MastodonPagedResponse<Account> = api.getAccountFollowing(
accountId = accountId,
olderThanId = olderThanId,
newerThanId = newerThanId,
maxId = maxId,
sinceId = sinceId,
limit = loadSize,
).toMastodonPagedResponse { it.toExternalModel() }

suspend fun getAccountStatuses(
accountId: String,
olderThanId: String? = null,
immediatelyNewerThanId: String? = null,
maxId: String? = null,
minId: String? = null,
loadSize: Int? = null,
onlyMedia: Boolean = false,
excludeReplies: Boolean = false,
excludeBoosts: Boolean = false,
): MastodonPagedResponse<Status> = api.getAccountStatuses(
accountId = accountId,
olderThanId = olderThanId,
immediatelyNewerThanId = immediatelyNewerThanId,
maxId = maxId,
minId = minId,
limit = loadSize,
onlyMedia = onlyMedia,
excludeReplies = excludeReplies,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import social.firefly.core.database.dao.FederatedTimelineStatusDao
import social.firefly.core.database.dao.HashTagTimelineStatusDao
import social.firefly.core.database.dao.HomeTimelineStatusDao
import social.firefly.core.database.dao.LocalTimelineStatusDao
import social.firefly.core.database.model.entities.statusCollections.AccountTimelineStatusWrapper
import social.firefly.core.database.model.entities.statusCollections.FederatedTimelineStatusWrapper
import social.firefly.core.database.model.entities.statusCollections.HashTagTimelineStatusWrapper
import social.firefly.core.database.model.entities.statusCollections.HomeTimelineStatusWrapper
Expand Down Expand Up @@ -243,26 +242,10 @@ class TimelineRepository internal constructor(
) =
accountTimelineStatusDao.upsertAll(statuses.map { it.toAccountTimelineStatus(timelineType) })

@ExperimentalPagingApi
fun getAccountTimelinePager(
fun accountTimelinePagingSource(
accountId: String,
timelineType: AccountTimelineType,
remoteMediator: RemoteMediator<Int, AccountTimelineStatusWrapper>,
pageSize: Int = 20,
initialLoadSize: Int = 40,
): Flow<PagingData<Status>> = Pager(
config = PagingConfig(
pageSize = pageSize,
initialLoadSize = initialLoadSize,
),
remoteMediator = remoteMediator,
) {
accountTimelineStatusDao.accountTimelinePagingSource(accountId, timelineType)
}.flow.map { pagingData ->
pagingData.map {
it.status.toExternalModel()
}
}
) = accountTimelineStatusDao.accountTimelinePagingSource(accountId, timelineType)

suspend fun deleteAccountTimeline(
accountId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ import social.firefly.core.model.paging.MastodonPagedResponse
import social.firefly.core.repository.paging.remotemediators.generic.IdBasedRemoteMediator
import social.firefly.core.repository.paging.remotemediators.generic.IndexBasedRemoteMediator

interface FfPager<T : Any, KEY: Any, DBO: Any> {
interface FfPager<PAGED_ITEM_TYPE : Any, KEY: Any, DATABASE_OBJECT: Any> {

fun mapDbObjectToExternalModel(dbo: DBO): T
fun mapDbObjectToExternalModel(dbo: DATABASE_OBJECT): PAGED_ITEM_TYPE

@OptIn(ExperimentalPagingApi::class)
fun getRemoteMediator(): RemoteMediator<KEY, DBO>
fun getRemoteMediator(): RemoteMediator<KEY, DATABASE_OBJECT>

fun pagingSource(): PagingSource<KEY, DBO>
fun pagingSource(): PagingSource<KEY, DATABASE_OBJECT>

@ExperimentalPagingApi
fun build(
pageSize: Int = 40,
initialLoadSize: Int = 40,
): Flow<PagingData<T>> = Pager(
): Flow<PagingData<PAGED_ITEM_TYPE>> = Pager(
config = PagingConfig(
pageSize = pageSize,
initialLoadSize = initialLoadSize,
Expand All @@ -38,23 +38,27 @@ interface FfPager<T : Any, KEY: Any, DBO: Any> {
}.flow.map { pagingData -> pagingData.map { mapDbObjectToExternalModel(it) } }
}

interface IndexBasedPager<T : Any, DBO: Any> : FfPager<T, Int, DBO> {
suspend fun saveLocally(items: List<PageItem<T>>, isRefresh: Boolean)
suspend fun getRemotely(limit: Int, offset: Int): List<T>
interface IndexBasedPager<PAGED_ITEM_TYPE : Any, DATABASE_OBJECT: Any> :
FfPager<PAGED_ITEM_TYPE, Int, DATABASE_OBJECT> {

suspend fun saveLocally(items: List<PageItem<PAGED_ITEM_TYPE>>, isRefresh: Boolean)
suspend fun getRemotely(limit: Int, offset: Int): List<PAGED_ITEM_TYPE>

@OptIn(ExperimentalPagingApi::class)
override fun getRemoteMediator(): RemoteMediator<Int, DBO> = IndexBasedRemoteMediator(
override fun getRemoteMediator(): RemoteMediator<Int, DATABASE_OBJECT> = IndexBasedRemoteMediator(
saveLocally = ::saveLocally,
getRemotely = ::getRemotely,
)
}

interface IdBasedPager<T : Any, DBO: Any> : FfPager<T, Int, DBO> {
suspend fun saveLocally(items: List<PageItem<T>>, isRefresh: Boolean)
suspend fun getRemotely(limit: Int, nextKey: String?): MastodonPagedResponse<T>
interface IdBasedPager<PAGED_ITEM_TYPE : Any, DATABASE_OBJECT: Any> :
FfPager<PAGED_ITEM_TYPE, Int, DATABASE_OBJECT> {

suspend fun saveLocally(items: List<PageItem<PAGED_ITEM_TYPE>>, isRefresh: Boolean)
suspend fun getRemotely(limit: Int, nextKey: String?): MastodonPagedResponse<PAGED_ITEM_TYPE>

@OptIn(ExperimentalPagingApi::class)
override fun getRemoteMediator(): RemoteMediator<Int, DBO> = IdBasedRemoteMediator(
override fun getRemoteMediator(): RemoteMediator<Int, DATABASE_OBJECT> = IdBasedRemoteMediator(
saveLocally = ::saveLocally,
getRemotely = ::getRemotely,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.module
import social.firefly.core.datastore.dataStoreModule
import social.firefly.core.repository.mastodon.mastodonRepositoryModule
import social.firefly.core.repository.paging.pagers.AccountTimelinePager
import social.firefly.core.repository.paging.pagers.BookmarksPager
import social.firefly.core.repository.paging.pagers.FollowedHashTagsPager
import social.firefly.core.repository.paging.pagers.TrendingHashTagPager
import social.firefly.core.repository.paging.pagers.TrendingStatusPager
import social.firefly.core.repository.paging.remotemediators.notifications.AllNotificationsRemoteMediator
import social.firefly.core.repository.paging.remotemediators.notifications.FollowNotificationsRemoteMediator
import social.firefly.core.repository.paging.remotemediators.notifications.MentionNotificationsRemoteMediator
import social.firefly.core.repository.paging.remotemediators.AccountTimelineRemoteMediator
import social.firefly.core.repository.paging.remotemediators.BlocksListRemoteMediator
import social.firefly.core.repository.paging.remotemediators.FavoritesRemoteMediator
import social.firefly.core.repository.paging.remotemediators.FederatedTimelineRemoteMediator
Expand Down Expand Up @@ -103,8 +103,14 @@ val pagingModule = module {
)
}

factoryOf(::TrendingStatusPager)
factoryOf(::TrendingHashTagPager)
factoryOf(::FollowedHashTagsPager)
factoryOf(::BookmarksPager)
factoryOf(::DomainBlocksPagingSource)

factory { parametersHolder ->
AccountTimelineRemoteMediator(
AccountTimelinePager(
accountRepository = get(),
saveStatusToDatabase = get(),
databaseDelegate = get(),
Expand All @@ -114,10 +120,4 @@ val pagingModule = module {
timelineType = parametersHolder[1],
)
}

factoryOf(::TrendingStatusPager)
factoryOf(::TrendingHashTagPager)
factoryOf(::FollowedHashTagsPager)
factoryOf(::BookmarksPager)
factoryOf(::DomainBlocksPagingSource)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package social.firefly.core.repository.paging.pagers

import androidx.paging.PagingSource
import social.firefly.core.database.model.entities.statusCollections.AccountTimelineStatusWrapper
import social.firefly.core.model.AccountTimelineType
import social.firefly.core.model.PageItem
import social.firefly.core.model.Status
import social.firefly.core.model.paging.MastodonPagedResponse
import social.firefly.core.repository.mastodon.AccountRepository
import social.firefly.core.repository.mastodon.DatabaseDelegate
import social.firefly.core.repository.mastodon.TimelineRepository
import social.firefly.core.repository.mastodon.model.status.toExternalModel
import social.firefly.core.repository.paging.IdBasedPager
import social.firefly.core.usecase.mastodon.status.GetInReplyToAccountNames
import social.firefly.core.usecase.mastodon.status.SaveStatusToDatabase

class AccountTimelinePager(
private val accountId: String,
private val timelineType: AccountTimelineType,
private val accountRepository: AccountRepository,
private val databaseDelegate: DatabaseDelegate,
private val timelineRepository: TimelineRepository,
private val saveStatusToDatabase: SaveStatusToDatabase,
private val getInReplyToAccountNames: GetInReplyToAccountNames,
) : IdBasedPager<Status, AccountTimelineStatusWrapper> {
override fun mapDbObjectToExternalModel(dbo: AccountTimelineStatusWrapper): Status =
dbo.status.toExternalModel()

override suspend fun saveLocally(items: List<PageItem<Status>>, isRefresh: Boolean) {
databaseDelegate.withTransaction {
if (isRefresh) {
timelineRepository.deleteAccountTimeline(accountId, timelineType)
}

val statuses = items.map { it.item }

saveStatusToDatabase(statuses)
timelineRepository.insertAllIntoAccountTimeline(statuses, timelineType)
}
}

override suspend fun getRemotely(limit: Int, nextKey: String?): MastodonPagedResponse<Status> {
val response = accountRepository.getAccountStatuses(
accountId = accountId,
maxId = nextKey,
minId = null,
loadSize = limit,
onlyMedia = timelineType == AccountTimelineType.MEDIA,
excludeReplies = timelineType == AccountTimelineType.POSTS,
)

return getInReplyToAccountNames(response)
}

override fun pagingSource(): PagingSource<Int, AccountTimelineStatusWrapper> =
timelineRepository.accountTimelinePagingSource(
accountId = accountId,
timelineType = timelineType,
)
}
Loading

0 comments on commit b11b10a

Please sign in to comment.