Skip to content

Commit

Permalink
feat: 新しいタイムラインを読み込めない問題を修正
Browse files Browse the repository at this point in the history
  • Loading branch information
pantasystem committed Sep 7, 2024
1 parent 938a90c commit f1a9e9d
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.Relation
import androidx.room.Transaction

/**
* キャッシュの塊を表すエンティティ
Expand Down Expand Up @@ -197,6 +198,18 @@ interface NotificationTimelineDAO {
)
suspend fun findNotificationsUntilId(timelineId: Long, untilId: String, limit: Int): List<NotificationWithDetails>

@Query(
"""
SELECT notifications.* FROM notification_timeline_items
JOIN notifications ON notification_timeline_items.notificationId = notifications.id
WHERE timelineId = :timelineId AND notifications.notification_id > :sinceId
ORDER BY notifications.notification_id DESC
LIMIT :limit
"""
)
@Transaction
suspend fun findNotificationsSinceId(timelineId: Long, sinceId: String, limit: Int): List<NotificationWithDetails>

// insert notification_timeline_items
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertNotificationItems(entity: List<NotificationTimelineItemEntity>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import net.pantasystem.milktea.api.mastodon.notification.MstNotificationDTO
import net.pantasystem.milktea.api.misskey.notification.NotificationDTO
import net.pantasystem.milktea.common.PageableState
import net.pantasystem.milktea.common.StateContent
import net.pantasystem.milktea.common.paginator.FuturePagingController
import net.pantasystem.milktea.common.paginator.PreviousPagingController
import net.pantasystem.milktea.common.runCancellableCatching
import net.pantasystem.milktea.data.infrastructure.notification.db.UnreadNotificationDAO
Expand Down Expand Up @@ -45,6 +46,13 @@ class NotificationPagingStoreImpl(
previousLoader = delegate,
)

private val futurePagingController = FuturePagingController(
entityConverter = delegate,
locker = delegate,
state = delegate,
futureLoader = delegate,
)

override val notifications: Flow<PageableState<List<NotificationRelation>>> =
delegate.state.map { state ->
state.suspendConvert { list ->
Expand All @@ -70,6 +78,13 @@ class NotificationPagingStoreImpl(
previousPagingController.loadPrevious().getOrThrow()
}

override suspend fun loadFuture(): Result<Int> = runCancellableCatching {
if (delegate.mutex.isLocked) {
return@runCancellableCatching -1
}
futurePagingController.loadFuture().getOrThrow()
}

override suspend fun onReceiveNewNotification(notificationRelation: NotificationRelation) {
delegate.mutex.withLock {
val state = delegate.getState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import kotlinx.coroutines.sync.Mutex
import net.pantasystem.milktea.common.PageableState
import net.pantasystem.milktea.common.StateContent
import net.pantasystem.milktea.common.paginator.EntityConverter
import net.pantasystem.milktea.common.paginator.FutureLoader
import net.pantasystem.milktea.common.paginator.IdGetter
import net.pantasystem.milktea.common.paginator.PaginationState
import net.pantasystem.milktea.common.paginator.PreviousLoader
Expand All @@ -21,7 +22,7 @@ import javax.inject.Singleton
class NotificationStoreImpl(
private val getAccount: suspend () -> Account,
private val notificationTimelineRepository: NotificationTimelineRepository,
) : StateLocker, PreviousLoader<Notification>, PaginationState<Notification.Id>,
) : StateLocker, PreviousLoader<Notification>, PaginationState<Notification.Id>, FutureLoader<Notification>,
IdGetter<String>, EntityConverter<Notification, Notification.Id> {

@Singleton
Expand Down Expand Up @@ -65,8 +66,17 @@ class NotificationStoreImpl(
untilId = getUntilId(),
).getOrThrow()
}

override suspend fun loadFuture(): Result<List<Notification>> = runCancellableCatching{
val account = getAccount()
notificationTimelineRepository.findLaterTimeline(
accountId = account.accountId,
sinceId = getSinceId(),
).getOrThrow()
}

override suspend fun getSinceId(): String? {
return null
return (_state.value.content as? StateContent.Exist)?.rawContent?.firstOrNull()?.notificationId
}

override suspend fun getUntilId(): String? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,33 @@ class NotificationTimelineRepositoryImpl @Inject constructor(
val models = if (untilId == null) {
dataBase.notificationTimelineDAO().findNotifications(timelineHolder.timeline.id, limit)
} else {
dataBase.notificationTimelineDAO().findNotificationsUntilId(timelineHolder.timeline.id, untilId, limit)
dataBase.notificationTimelineDAO()
.findNotificationsUntilId(timelineHolder.timeline.id, untilId, limit)
}.map {
it.toModel()
}
val lastInitialFetchTime = lastFetchTimeMap[accountId] ?: System.currentTimeMillis()
val now = System.currentTimeMillis()
val fetched = if (models.size < limit) {
fetch(timelineHolder.timeline.id, account, untilId, excludeTypes, includeTypes).getOrThrow()
fetch(
timelineHolder.timeline.id,
account,
untilId,
null,
excludeTypes,
includeTypes
).getOrThrow()
} else {
coroutineScope.launch {
if (now - lastInitialFetchTime > 1000 * 60 * 3) {
fetch(timelineHolder.timeline.id, account, untilId, excludeTypes, includeTypes)
fetch(
timelineHolder.timeline.id,
account,
untilId,
null,
excludeTypes,
includeTypes
)
}
}
models
Expand All @@ -74,23 +89,91 @@ class NotificationTimelineRepositoryImpl @Inject constructor(
fetched
}

override suspend fun findLaterTimeline(
accountId: Long,
sinceId: String?,
limit: Int,
excludeTypes: List<String>?,
includeTypes: List<String>?
): Result<List<Notification>> = runCancellableCatching {
val timelineHolder = makeNotificationTimelineHolder(
accountId = accountId,
excludeTypes = excludeTypes ?: emptyList(),
includeTypes = includeTypes ?: emptyList()
)
val account = accountRepository.get(accountId).getOrThrow()
val models = if (sinceId == null) {
dataBase.notificationTimelineDAO().findNotifications(timelineHolder.timeline.id, limit)
} else {
dataBase.notificationTimelineDAO()
.findNotificationsSinceId(timelineHolder.timeline.id, sinceId, limit)
}.map {
it.toModel()
}
val lastInitialFetchTime = lastFetchTimeMap[accountId] ?: System.currentTimeMillis()
val now = System.currentTimeMillis()
val fetched = if (models.size < limit) {
fetch(
timelineHolder.timeline.id,
account,
null,
sinceId,
excludeTypes,
includeTypes
).getOrThrow()
} else {
coroutineScope.launch {
if (now - lastInitialFetchTime > 1000 * 60 * 3) {
fetch(
timelineHolder.timeline.id,
account,
null,
sinceId,
excludeTypes,
includeTypes
)
}
}
models
}

if (sinceId == null) {
lastFetchTimeMap += accountId to now
}

fetched
}

private suspend fun fetch(
timelineId: Long,
account: Account,
untilId: String?,
sinceId: String?,
excludeTypes: List<String>?,
includeTypes: List<String>?,
): Result<List<Notification>> =
runCancellableCatching {
when (account.instanceType) {
Account.InstanceType.MISSKEY, Account.InstanceType.FIREFISH -> {
fetchMisskeyNotifications(account, untilId, excludeTypes, includeTypes).map {
fetchMisskeyNotifications(
account,
untilId,
sinceId,
excludeTypes,
includeTypes
).map {
notificationAdder.addAndConvert(account, it).notification
}
}

Account.InstanceType.MASTODON, Account.InstanceType.PLEROMA -> {
fetchMastodonNotifications(account, untilId, excludeTypes, includeTypes).map {
fetchMastodonNotifications(
account,
untilId,
sinceId,
excludeTypes,
includeTypes
).map {
notificationAdder.addConvert(account, it).notification
}
}
Expand All @@ -99,7 +182,10 @@ class NotificationTimelineRepositoryImpl @Inject constructor(
notifications.map {
NotificationTimelineItemEntity(
timelineId = timelineId,
notificationId = NotificationEntity.makeId(it.id.accountId, it.id.notificationId),
notificationId = NotificationEntity.makeId(
it.id.accountId,
it.id.notificationId
),
cachedAt = System.currentTimeMillis(),
)
}
Expand All @@ -110,13 +196,15 @@ class NotificationTimelineRepositoryImpl @Inject constructor(
private suspend fun fetchMisskeyNotifications(
account: Account,
untilId: String?,
sinceId: String?,
excludeTypes: List<String>?,
includeTypes: List<String>?,
): List<NotificationDTO> {
val res = misskeyAPIProvider.get(account).notification(
NotificationRequest(
i = account.token,
untilId = untilId,
sinceId = sinceId,
excludeTypes = excludeTypes,
includeTypes = includeTypes,
)
Expand All @@ -127,11 +215,13 @@ class NotificationTimelineRepositoryImpl @Inject constructor(
private suspend fun fetchMastodonNotifications(
account: Account,
untilId: String?,
sinceId: String?,
excludeTypes: List<String>?,
includeTypes: List<String>?,
): List<MstNotificationDTO> {
val res = mastodonAPIProvider.get(account).getNotifications(
maxId = untilId,
minId = sinceId,
excludeTypes = excludeTypes,
types = includeTypes,
)
Expand All @@ -151,8 +241,7 @@ class NotificationTimelineRepositoryImpl @Inject constructor(
dao.findByExcludeTypes(accountId, excludeTypes).firstOrNull()
} else if (excludeTypes.isEmpty()) {
dao.findByIncludeTypes(accountId, includeTypes).firstOrNull()
}
else {
} else {
dao.findByExcludeTypesAndIncludeTypes(
accountId = accountId,
excludeTypes = excludeTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class NotificationFragment : Fragment(R.layout.fragment_notification) {

if (firstVisibleItemPosition == 0) {
Log.d("", "先頭")
mViewModel.loadFuture()
}

if (endVisibleItemPosition == (itemCount - 1)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,27 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import net.pantasystem.milktea.app_store.account.AccountStore
import net.pantasystem.milktea.common.*
import net.pantasystem.milktea.common.APIError
import net.pantasystem.milktea.common.Logger
import net.pantasystem.milktea.common.PageableState
import net.pantasystem.milktea.common.StateContent
import net.pantasystem.milktea.common.flatMapCancellableCatching
import net.pantasystem.milktea.common.runCancellableCatching
import net.pantasystem.milktea.common_android.resource.StringSource
import net.pantasystem.milktea.common_android_ui.APIErrorStringConverter
import net.pantasystem.milktea.model.account.Account
Expand All @@ -22,7 +38,12 @@ import net.pantasystem.milktea.model.account.page.Pageable
import net.pantasystem.milktea.model.filter.WordFilterService
import net.pantasystem.milktea.model.group.AcceptGroupInvitationUseCase
import net.pantasystem.milktea.model.group.RejectGroupInvitationUseCase
import net.pantasystem.milktea.model.notification.*
import net.pantasystem.milktea.model.notification.GroupInvitedNotification
import net.pantasystem.milktea.model.notification.Notification
import net.pantasystem.milktea.model.notification.NotificationPagingStore
import net.pantasystem.milktea.model.notification.NotificationRepository
import net.pantasystem.milktea.model.notification.NotificationStreaming
import net.pantasystem.milktea.model.notification.ReceiveFollowRequestNotification
import net.pantasystem.milktea.model.setting.LocalConfigRepository
import net.pantasystem.milktea.model.user.follow.requests.AcceptFollowRequestUseCase
import net.pantasystem.milktea.model.user.follow.requests.RejectFollowRequestUseCase
Expand Down Expand Up @@ -172,6 +193,15 @@ class NotificationViewModel @Inject constructor(
}
}

fun loadFuture() {
viewModelScope.launch {
notificationPagingStore.loadFuture().onFailure {
logger.error("通知の読み込みに失敗", it)
_error.tryEmit(it)
}
}
}

fun acceptFollowRequest(notification: Notification) {
if (notification is ReceiveFollowRequestNotification) {
viewModelScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ interface NotificationPagingStore {

suspend fun loadPrevious(): Result<Int>

suspend fun loadFuture(): Result<Int>

suspend fun clear()

suspend fun onReceiveNewNotification(notificationRelation: NotificationRelation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ interface NotificationTimelineRepository {
excludeTypes: List<String>? = null,
includeTypes: List<String>? = null
): Result<List<Notification>>

suspend fun findLaterTimeline(
accountId: Long,
sinceId: String? = null,
limit: Int = 10,
excludeTypes: List<String>? = null,
includeTypes: List<String>? = null,
): Result<List<Notification>>
}

0 comments on commit f1a9e9d

Please sign in to comment.