Skip to content

Commit

Permalink
fix(greader): mark all as read
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashinch committed Feb 5, 2024
1 parent f14aecb commit 4ec641f
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 64 deletions.
42 changes: 38 additions & 4 deletions app/src/main/java/me/ash/reader/domain/repository/ArticleDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,13 @@ interface ArticleDao {
before: Date,
)

@Transaction
@Query(
"""
UPDATE article SET isUnread = :isUnread
WHERE accountId = :accountId
AND date < :before
AND isUnread != :isUnread
"""
)
suspend fun markAllAsRead(
Expand All @@ -299,6 +301,7 @@ interface ArticleDao {
before: Date,
)

@Transaction
@Query(
"""
UPDATE article SET isUnread = :isUnread
Expand All @@ -307,6 +310,7 @@ interface ArticleDao {
WHERE groupId = :groupId
)
AND accountId = :accountId
AND isUnread != :isUnread
AND date < :before
"""
)
Expand All @@ -317,11 +321,13 @@ interface ArticleDao {
before: Date,
)

@Transaction
@Query(
"""
UPDATE article SET isUnread = :isUnread
WHERE feedId = :feedId
AND accountId = :accountId
AND isUnread != :isUnread
AND date < :before
"""
)
Expand All @@ -332,6 +338,7 @@ interface ArticleDao {
before: Date,
)

@Transaction
@Query(
"""
UPDATE article SET isUnread = :isUnread
Expand Down Expand Up @@ -632,6 +639,17 @@ interface ArticleDao {
)
fun queryMetadataAll(accountId: Int): List<ArticleMeta>

@Transaction
@Query(
"""
SELECT id, isUnread, isStarred FROM article
WHERE accountId = :accountId
AND isUnread = :isUnread
ORDER BY date DESC
"""
)
fun queryMetadataAll(accountId: Int, isUnread: Boolean): List<ArticleMeta>

@Transaction
@Query(
"""
Expand All @@ -643,28 +661,42 @@ interface ArticleDao {
)
fun queryMetadataAll(accountId: Int, before: Date): List<ArticleMeta>

@Transaction
@Query(
"""
SELECT id, isUnread, isStarred FROM article
WHERE accountId = :accountId
AND isUnread = :isUnread
AND date < :before
ORDER BY date DESC
"""
)
fun queryMetadataAll(accountId: Int, isUnread: Boolean, before: Date): List<ArticleMeta>

@Transaction
@Query(
"""
SELECT id, isUnread, isStarred FROM article
WHERE accountId = :accountId
AND feedId = :feedId
AND isUnread = :isUnread
ORDER BY date DESC
"""
)
fun queryMetadataByFeedId(accountId: Int, feedId: String): List<ArticleMeta>
fun queryMetadataByFeedId(accountId: Int, feedId: String, isUnread: Boolean): List<ArticleMeta>

@Transaction
@Query(
"""
SELECT id, isUnread, isStarred FROM article
WHERE accountId = :accountId
AND feedId = :feedId
AND isUnread = :isUnread
AND date < :before
ORDER BY date DESC
"""
)
fun queryMetadataByFeedId(accountId: Int, feedId: String, before: Date): List<ArticleMeta>
fun queryMetadataByFeedId(accountId: Int, feedId: String, isUnread: Boolean, before: Date): List<ArticleMeta>

@Transaction
@Query(
Expand All @@ -675,10 +707,11 @@ interface ArticleDao {
LEFT JOIN `group` AS c ON c.id = b.groupId
WHERE c.id = :groupId
AND a.accountId = :accountId
AND a.isUnread = :isUnread
ORDER BY a.date DESC
"""
)
fun queryMetadataByGroupId(accountId: Int, groupId: String): List<ArticleMeta>
fun queryMetadataByGroupIdWhenIsUnread(accountId: Int, groupId: String, isUnread: Boolean): List<ArticleMeta>

@Transaction
@Query(
Expand All @@ -689,11 +722,12 @@ interface ArticleDao {
LEFT JOIN `group` AS c ON c.id = b.groupId
WHERE c.id = :groupId
AND a.accountId = :accountId
AND a.isUnread = :isUnread
AND a.date < :before
ORDER BY a.date DESC
"""
)
fun queryMetadataByGroupId(accountId: Int, groupId: String, before: Date): List<ArticleMeta>
fun queryMetadataByGroupIdWhenIsUnread(accountId: Int, groupId: String, isUnread: Boolean, before: Date): List<ArticleMeta>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg article: Article)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ abstract class AbstractRssRepository(
)
}

feedId != null && articleId == null -> {
feedId != null -> {
articleDao.markAllAsReadByFeedId(
accountId = accountId,
feedId = feedId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,16 @@ class GoogleReaderRssService @Inject constructor(
}

/**
* This is a reference to Reeder's synchronization logic,
* This is improved from Reeder's synchronization strategy,
* which syncs well across multiple devices.
*
* 1. Fetch tags (not supported yet)
* 2. Fetch folder and subscription list
* 3. Fetch all unread item id list
* 4. Fetch all starred item id list
* 5. Fetch unread contents of items with differences
* 5. Fetch unread contents of items with differences (up to 10k items per sync process)
* 6. Fetch starred contents of items with differences
* 7. Fetch read contents of items with differences
* 7. Fetch read contents of items with differences (up to one month old)
* 8. Remove orphaned groups and feeds, after synchronizing the starred/un-starred
*
* The following link contains other great synchronization logic,
Expand Down Expand Up @@ -277,16 +277,16 @@ class GoogleReaderRssService @Inject constructor(
googleReaderAPI.getUnreadItemIds(continuationId = it)
}.toSet()
Log.i("RLog", "sync unreadIds size: ${unreadIds.size}")
val toBeUnread = unreadIds - localUnreadIds
val toBeUnread = (unreadIds - localUnreadIds).run {
if (size > 10000) take(10000).toSet() else this
}
Log.i("RLog", "sync toBeUnread size: ${toBeUnread.size}")
if (toBeUnread.isNotEmpty()) {
toBeUnread.chunked(999).forEach {
articleDao.markAsReadByIdSet(
accountId = accountId,
ids = it.toSet(),
isUnread = true,
)
}
toBeUnread.takeIf { it.isNotEmpty() }?.chunked(500)?.forEach {
articleDao.markAsReadByIdSet(
accountId = accountId,
ids = it.toSet(),
isUnread = true,
)
}

// 4. Fetch all starred item id list
Expand All @@ -296,17 +296,15 @@ class GoogleReaderRssService @Inject constructor(
Log.i("RLog", "sync starredIds size: ${starredIds.size}")
val toBeStarred = starredIds - localStarredIds
Log.i("RLog", "sync toBeStarred size: ${toBeStarred.size}")
if (toBeStarred.isNotEmpty()) {
toBeStarred.chunked(999).forEach {
articleDao.markAsStarredByIdSet(
accountId = accountId,
ids = it.toSet(),
isStarred = true,
)
}
toBeStarred.takeIf { it.isNotEmpty() }?.chunked(500)?.forEach {
articleDao.markAsStarredByIdSet(
accountId = accountId,
ids = it.toSet(),
isStarred = true,
)
}

// 5. Fetch unread contents of items with differences
// 5. Fetch unread contents of items with differences (up to 10k items per sync process)
fetchItemsContents(
itemIds = toBeUnread,
googleReaderAPI = googleReaderAPI,
Expand All @@ -328,14 +326,14 @@ class GoogleReaderRssService @Inject constructor(
preDate = preDate,
)

// 7. Fetch read contents of items with differences
// 7. Fetch read contents of items with differences (up to one month old)
val readIds = fetchItemIdsAndContinue {
googleReaderAPI.getReadItemIds(since = lastMonthAt, continuationId = it)
}.toSet()
Log.i("RLog", "sync readIds size: ${readIds.size}")
val localReadIds = articleDao.queryMetadataAll(accountId).filter { !it.isUnread }
.map { it.id.dollarLast() }.toSet()
val toBeRead = readIds - unreadIds - localReadIds
var toBeRead = readIds - unreadIds - localReadIds
Log.i("RLog", "sync toBeRead size: ${toBeRead.size}")
if (toBeRead.isNotEmpty()) {
fetchItemsContents(
Expand All @@ -348,6 +346,16 @@ class GoogleReaderRssService @Inject constructor(
preDate = preDate,
)
}
// Sync the read status of articles prior to last month
toBeRead = localUnreadIds - unreadIds
Log.i("RLog", "sync toBeRead (last month) size: ${toBeRead.size}")
toBeRead.takeIf { it.isNotEmpty() }?.chunked(500)?.forEach {
articleDao.markAsReadByIdSet(
accountId = accountId,
ids = it.toSet(),
isUnread = false,
)
}

// 8. Remove orphaned groups and feeds, after synchronizing the starred/un-starred
groupDao.queryAll(accountId)
Expand Down Expand Up @@ -428,23 +436,25 @@ class GoogleReaderRssService @Inject constructor(
before: Date?,
isUnread: Boolean,
) {
super.markAsRead(groupId, feedId, articleId, before, isUnread)
val accountId = context.currentAccountId
val googleReaderAPI = getGoogleReaderAPI()
val markList: List<String> = when {
groupId != null -> {
if (before == null) {
articleDao.queryMetadataByGroupId(accountId, groupId)
articleDao.queryMetadataByGroupIdWhenIsUnread(accountId, groupId, !isUnread)
} else {
articleDao.queryMetadataByGroupId(accountId, groupId, before)
articleDao.queryMetadataByGroupIdWhenIsUnread(accountId,
groupId,
!isUnread,
before)
}.map { it.id.dollarLast() }
}

feedId != null -> {
if (before == null) {
articleDao.queryMetadataByFeedId(accountId, feedId)
articleDao.queryMetadataByFeedId(accountId, feedId, !isUnread)
} else {
articleDao.queryMetadataByFeedId(accountId, feedId, before)
articleDao.queryMetadataByFeedId(accountId, feedId, !isUnread, before)
}.map { it.id.dollarLast() }
}

Expand All @@ -454,17 +464,21 @@ class GoogleReaderRssService @Inject constructor(

else -> {
if (before == null) {
articleDao.queryMetadataAll(accountId)
articleDao.queryMetadataAll(accountId, !isUnread)
} else {
articleDao.queryMetadataAll(accountId, before)
articleDao.queryMetadataAll(accountId, !isUnread, before)
}.map { it.id.dollarLast() }
}
}
if (markList.isNotEmpty()) googleReaderAPI.editTag(
itemIds = markList,
mark = if (!isUnread) GoogleReaderAPI.Stream.READ.tag else null,
unmark = if (isUnread) GoogleReaderAPI.Stream.READ.tag else null,
)
super.markAsRead(groupId, feedId, articleId, before, isUnread)
markList.takeIf { it.isNotEmpty() }?.chunked(500)?.forEach {
Log.d("RLog", "sync markAsRead: ${it.size} num")
googleReaderAPI.editTag(
itemIds = it,
mark = if (!isUnread) GoogleReaderAPI.Stream.READ.tag else null,
unmark = if (isUnread) GoogleReaderAPI.Stream.READ.tag else null,
)
}
}

override suspend fun markAsStarred(articleId: String, isStarred: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ class GoogleReaderAPI private constructor(
retryablePostRequest<String>(
query = "reader/api/0/edit-tag",
form = mutableListOf<Pair<String, String>>().apply {
itemIds.forEach { add(Pair("i", it.ofItemIdToStreamId())) }
itemIds.forEach { add(Pair("i", it.ofItemIdToHexId())) }
mark?.let { add(Pair("a", mark)) }
unmark?.let { add(Pair("r", unmark)) }
}
Expand Down
13 changes: 9 additions & 4 deletions app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package me.ash.reader.ui.page.home

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
Expand All @@ -10,16 +9,22 @@ import androidx.work.WorkManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import me.ash.reader.domain.model.article.ArticleFlowItem
import me.ash.reader.domain.model.article.mapPagingFlowItem
import me.ash.reader.domain.model.feed.Feed
import me.ash.reader.domain.model.general.Filter
import me.ash.reader.domain.model.group.Group
import me.ash.reader.domain.service.RssService
import me.ash.reader.infrastructure.android.AndroidStringsHelper
import me.ash.reader.domain.service.SyncWorker
import me.ash.reader.infrastructure.android.AndroidStringsHelper
import me.ash.reader.infrastructure.di.ApplicationScope
import me.ash.reader.infrastructure.di.IODispatcher
import javax.inject.Inject
Expand All @@ -44,7 +49,7 @@ class HomeViewModel @Inject constructor(
val syncWorkLiveData = workManager.getWorkInfosByTagLiveData(SyncWorker.WORK_NAME)

fun sync() {
viewModelScope.launch(ioDispatcher) {
applicationScope.launch(ioDispatcher) {
rssService.get().doSync()
}
}
Expand Down
Loading

0 comments on commit 4ec641f

Please sign in to comment.