Improve local sync
This commit is contained in:
		
							parent
							
								
									b2fe0674c8
								
							
						
					
					
						commit
						ada579377b
					
				@ -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)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user