Improve local sync

This commit is contained in:
Ash 2022-03-23 18:50:13 +08:00
parent b2fe0674c8
commit ada579377b
2 changed files with 131 additions and 117 deletions

View File

@ -7,11 +7,13 @@ import androidx.paging.PagingSource
import androidx.work.*
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import me.ash.reader.DataStoreKeys
import me.ash.reader.data.account.AccountDao
import me.ash.reader.data.article.Article
@ -109,21 +111,23 @@ abstract class AbstractRssRepository constructor(
}
}
fun pullImportant(
suspend fun pullImportant(
isStarred: Boolean = false,
isUnread: Boolean = false,
): Flow<List<ImportantCount>> {
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)!!
Log.i(
"RLog",
"pullImportant: accountId: ${accountId}, isStarred: ${isStarred}, isUnread: ${isUnread}"
)
return when {
isStarred -> articleDao
.queryImportantCountWhenIsStarred(accountId, isStarred)
isUnread -> articleDao
.queryImportantCountWhenIsUnread(accountId, isUnread)
else -> articleDao.queryImportantCountWhenIsAll(accountId)
return withContext(Dispatchers.IO) {
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)!!
Log.i(
"RLog",
"pullImportant: accountId: ${accountId}, isStarred: ${isStarred}, isUnread: ${isUnread}"
)
when {
isStarred -> articleDao
.queryImportantCountWhenIsStarred(accountId, isStarred)
isUnread -> articleDao
.queryImportantCountWhenIsUnread(accountId, isUnread)
else -> articleDao.queryImportantCountWhenIsAll(accountId)
}
}
}

View File

@ -12,8 +12,11 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat.getSystemService
import androidx.work.WorkManager
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import me.ash.reader.*
import me.ash.reader.data.account.AccountDao
import me.ash.reader.data.article.Article
@ -42,6 +45,22 @@ class LocalRssRepository @Inject constructor(
context, accountDao, articleDao, groupDao,
feedDao, rssNetworkDataSource, workManager,
) {
private val notificationManager: NotificationManager =
(getSystemService(
context,
NotificationManager::class.java
) as NotificationManager).also {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
it.createNotificationChannel(
NotificationChannel(
NotificationGroupName.ARTICLE_UPDATE,
NotificationGroupName.ARTICLE_UPDATE,
NotificationManager.IMPORTANCE_DEFAULT
)
)
}
}
override suspend fun updateArticleInfo(article: Article) {
articleDao.update(article)
}
@ -68,121 +87,112 @@ class LocalRssRepository @Inject constructor(
override suspend fun sync() {
mutex.withLock {
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)
?: return
val feeds = feedDao.queryAll(accountId)
val feedNotificationMap = mutableMapOf<String, Boolean>()
feeds.forEach { feed ->
feedNotificationMap[feed.id] = feed.isNotification
}
val preTime = System.currentTimeMillis()
val chunked = feeds.chunked(6)
chunked.forEachIndexed { index, item ->
item.forEach {
Log.i("RlOG", "chunked $index: ${it.name}")
withContext(Dispatchers.IO) {
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)
?: return@withContext
val feeds = async { feedDao.queryAll(accountId) }
val articles = feeds.await().also { feed ->
updateSyncState {
it.copy(
feedCount = feed.size,
)
}
}.map { feed ->
async {
val articles = syncFeed(accountId, feed)
articles
}
}
}
val flows = mutableListOf<Flow<List<Article>>>()
repeat(chunked.size) {
flows.add(flow {
val articles = mutableListOf<Article>()
chunked[it].forEach { feed ->
val latest = articleDao.queryLatestByFeedId(accountId, feed.id)
articles.addAll(
rssHelper.queryRssXml(
rssNetworkDataSource,
accountId,
feed,
latest?.title,
).also {
if (feed.icon == null && it.isNotEmpty()) {
rssHelper.queryRssIcon(feedDao, feed, it.first().link)
}
articles.awaitAll().sumOf { it.size }.let { count ->
Log.i(
"RlOG",
"[${count}] onCompletion: ${System.currentTimeMillis() - preTime}"
)
accountDao.queryById(accountId)?.let { account ->
accountDao.update(
account.apply {
updateAt = Date()
}
)
updateSyncState {
it.copy(
feedCount = feeds.size,
syncedCount = it.syncedCount + 1,
currentFeedName = feed.name
)
}
}
emit(articles)
})
}
combine(
flows
) {
val notificationManager: NotificationManager =
getSystemService(
context,
NotificationManager::class.java
) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.createNotificationChannel(
NotificationChannel(
NotificationGroupName.ARTICLE_UPDATE,
"文章更新",
NotificationManager.IMPORTANCE_DEFAULT
updateSyncState {
it.copy(
feedCount = 0,
syncedCount = 0,
currentFeedName = ""
)
)
}
it.forEach { articleList ->
val ids = articleDao.insertList(articleList)
articleList.forEachIndexed { index, article ->
Log.i("RlOG", "combine ${article.feedId}: ${article.title}")
if (feedNotificationMap[article.feedId] == true) {
val builder = NotificationCompat.Builder(
context,
NotificationGroupName.ARTICLE_UPDATE
).setSmallIcon(R.drawable.ic_launcher_foreground)
.setGroup(NotificationGroupName.ARTICLE_UPDATE)
.setContentTitle(article.title)
.setContentText(article.shortDescription)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(
PendingIntent.getActivity(
context,
ids[index].toInt(),
Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(
ExtraName.ARTICLE_ID,
ids[index]
)
},
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
notificationManager.notify(
ids[index].toInt(),
builder.build().apply {
flags = Notification.FLAG_AUTO_CANCEL
}
)
}
}
}
}.buffer().onCompletion {
val afterTime = System.currentTimeMillis()
Log.i("RlOG", "onCompletion: ${afterTime - preTime}")
accountDao.queryById(accountId)?.let { account ->
accountDao.update(
account.apply {
updateAt = Date()
}
)
}
}
}
private suspend fun syncFeed(
accountId: Int,
feed: Feed
): MutableList<Article> {
val articles = mutableListOf<Article>()
val latest = articleDao.queryLatestByFeedId(accountId, feed.id)
articles.addAll(
rssHelper.queryRssXml(
rssNetworkDataSource,
accountId,
feed,
latest?.title,
).also {
if (feed.icon == null && it.isNotEmpty()) {
rssHelper.queryRssIcon(feedDao, feed, it.first().link)
}
updateSyncState {
it.copy(
feedCount = 0,
syncedCount = 0,
currentFeedName = ""
}
)
updateSyncState {
it.copy(
syncedCount = it.syncedCount + 1,
currentFeedName = feed.name
)
}
articleDao.insertList(articles)
if (feed.isNotification) {
notify(articles)
}
return articles
}
private fun notify(
articles: MutableList<Article>,
) {
articles.forEach { article ->
val builder = NotificationCompat.Builder(
context,
NotificationGroupName.ARTICLE_UPDATE
).setSmallIcon(R.drawable.ic_launcher_foreground)
.setGroup(NotificationGroupName.ARTICLE_UPDATE)
.setContentTitle(article.title)
.setContentText(article.shortDescription)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(
PendingIntent.getActivity(
context,
Random().nextInt() + article.id.hashCode(),
Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(
ExtraName.ARTICLE_ID,
article.id
)
},
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
notificationManager.notify(
Random().nextInt() + article.id.hashCode(),
builder.build().apply {
flags = Notification.FLAG_AUTO_CANCEL
}
}.collect()
)
}
}
}