Refactor repository coroutine

This commit is contained in:
Ash 2022-04-01 02:06:59 +08:00
parent 004005d8be
commit e1e43019f5
14 changed files with 389 additions and 341 deletions

View File

@ -5,10 +5,11 @@ import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.ash.reader.data.module.ApplicationScope import me.ash.reader.data.module.ApplicationScope
import me.ash.reader.data.module.DispatcherDefault
import me.ash.reader.data.repository.* import me.ash.reader.data.repository.*
import me.ash.reader.data.source.OpmlLocalDataSource import me.ash.reader.data.source.OpmlLocalDataSource
import me.ash.reader.data.source.ReaderDatabase import me.ash.reader.data.source.ReaderDatabase
@ -57,9 +58,13 @@ class App : Application(), Configuration.Provider {
@ApplicationScope @ApplicationScope
lateinit var applicationScope: CoroutineScope lateinit var applicationScope: CoroutineScope
@Inject
@DispatcherDefault
lateinit var dispatcherDefault: CoroutineDispatcher
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
applicationScope.launch(Dispatchers.IO) { applicationScope.launch(dispatcherDefault) {
accountInit() accountInit()
workerInit() workerInit()
} }

View File

@ -17,6 +17,8 @@ import java.io.IOException
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings") val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
val Context.currentAccountId: Int val Context.currentAccountId: Int
get() = this.dataStore.get(DataStoreKeys.CurrentAccountId)!! get() = this.dataStore.get(DataStoreKeys.CurrentAccountId)!!
val Context.currentAccountType: Int
get() = this.dataStore.get(DataStoreKeys.CurrentAccountType)!!
suspend fun <T> DataStore<Preferences>.put(dataStoreKeys: DataStoreKeys<T>, value: T) { suspend fun <T> DataStore<Preferences>.put(dataStoreKeys: DataStoreKeys<T>, value: T) {
this.edit { this.edit {

View File

@ -7,7 +7,7 @@ import androidx.paging.PagingSource
import androidx.work.* import androidx.work.*
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import me.ash.reader.currentAccountId import me.ash.reader.currentAccountId
@ -32,6 +32,7 @@ abstract class AbstractRssRepository constructor(
private val feedDao: FeedDao, private val feedDao: FeedDao,
private val rssNetworkDataSource: RssNetworkDataSource, private val rssNetworkDataSource: RssNetworkDataSource,
private val workManager: WorkManager, private val workManager: WorkManager,
private val dispatcherIO: CoroutineDispatcher,
) { ) {
data class SyncState( data class SyncState(
val feedCount: Int = 0, val feedCount: Int = 0,
@ -59,11 +60,11 @@ abstract class AbstractRssRepository constructor(
} }
fun pullGroups(): Flow<MutableList<Group>> { fun pullGroups(): Flow<MutableList<Group>> {
return groupDao.queryAllGroup(context.currentAccountId).flowOn(Dispatchers.IO) return groupDao.queryAllGroup(context.currentAccountId).flowOn(dispatcherIO)
} }
fun pullFeeds(): Flow<MutableList<GroupWithFeed>> { fun pullFeeds(): Flow<MutableList<GroupWithFeed>> {
return groupDao.queryAllGroupWithFeedAsFlow(context.currentAccountId).flowOn(Dispatchers.IO) return groupDao.queryAllGroupWithFeedAsFlow(context.currentAccountId).flowOn(dispatcherIO)
} }
fun pullArticles( fun pullArticles(
@ -72,7 +73,6 @@ abstract class AbstractRssRepository constructor(
isStarred: Boolean = false, isStarred: Boolean = false,
isUnread: Boolean = false, isUnread: Boolean = false,
): PagingSource<Int, ArticleWithFeed> { ): PagingSource<Int, ArticleWithFeed> {
Log.i("RLog", "thread:pullArticles ${Thread.currentThread().name}")
val accountId = context.currentAccountId val accountId = context.currentAccountId
Log.i( Log.i(
"RLog", "RLog",
@ -107,7 +107,6 @@ abstract class AbstractRssRepository constructor(
isStarred: Boolean = false, isStarred: Boolean = false,
isUnread: Boolean = false, isUnread: Boolean = false,
): Flow<List<ImportantCount>> { ): Flow<List<ImportantCount>> {
Log.i("RLog", "thread:pullImportant ${Thread.currentThread().name}")
val accountId = context.currentAccountId val accountId = context.currentAccountId
Log.i( Log.i(
"RLog", "RLog",
@ -119,7 +118,7 @@ abstract class AbstractRssRepository constructor(
isUnread -> articleDao isUnread -> articleDao
.queryImportantCountWhenIsUnread(accountId, isUnread) .queryImportantCountWhenIsUnread(accountId, isUnread)
else -> articleDao.queryImportantCountWhenIsAll(accountId) else -> articleDao.queryImportantCountWhenIsAll(accountId)
}.flowOn(Dispatchers.IO) }.flowOn(dispatcherIO)
} }
suspend fun findFeedById(id: String): Feed? { suspend fun findFeedById(id: String): Feed? {
@ -130,7 +129,7 @@ abstract class AbstractRssRepository constructor(
return articleDao.queryById(id) return articleDao.queryById(id)
} }
suspend fun isExist(url: String): Boolean { suspend fun isFeedExist(url: String): Boolean {
return feedDao.queryByLink(context.currentAccountId, url).isNotEmpty() return feedDao.queryByLink(context.currentAccountId, url).isNotEmpty()
} }

View File

@ -4,8 +4,11 @@ import android.content.Context
import android.util.Log import android.util.Log
import androidx.work.WorkManager import androidx.work.WorkManager
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import me.ash.reader.* import me.ash.reader.currentAccountId
import me.ash.reader.data.account.AccountDao import me.ash.reader.data.account.AccountDao
import me.ash.reader.data.article.Article import me.ash.reader.data.article.Article
import me.ash.reader.data.article.ArticleDao import me.ash.reader.data.article.ArticleDao
@ -13,11 +16,16 @@ import me.ash.reader.data.feed.Feed
import me.ash.reader.data.feed.FeedDao import me.ash.reader.data.feed.FeedDao
import me.ash.reader.data.group.Group import me.ash.reader.data.group.Group
import me.ash.reader.data.group.GroupDao import me.ash.reader.data.group.GroupDao
import me.ash.reader.data.module.ApplicationScope
import me.ash.reader.data.module.DispatcherDefault
import me.ash.reader.data.module.DispatcherIO
import me.ash.reader.data.source.FeverApiDataSource import me.ash.reader.data.source.FeverApiDataSource
import me.ash.reader.data.source.RssNetworkDataSource import me.ash.reader.data.source.RssNetworkDataSource
import me.ash.reader.spacerDollar
import net.dankito.readability4j.extended.Readability4JExtended import net.dankito.readability4j.extended.Readability4JExtended
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.set
class FeverRssRepository @Inject constructor( class FeverRssRepository @Inject constructor(
@ApplicationContext @ApplicationContext
@ -29,10 +37,17 @@ class FeverRssRepository @Inject constructor(
private val feverApiDataSource: FeverApiDataSource, private val feverApiDataSource: FeverApiDataSource,
private val accountDao: AccountDao, private val accountDao: AccountDao,
rssNetworkDataSource: RssNetworkDataSource, rssNetworkDataSource: RssNetworkDataSource,
@ApplicationScope
private val applicationScope: CoroutineScope,
@DispatcherDefault
private val dispatcherDefault: CoroutineDispatcher,
@DispatcherIO
private val dispatcherIO: CoroutineDispatcher,
workManager: WorkManager, workManager: WorkManager,
) : AbstractRssRepository( ) : AbstractRssRepository(
context, accountDao, articleDao, groupDao, context, accountDao, articleDao, groupDao,
feedDao, rssNetworkDataSource, workManager, feedDao, rssNetworkDataSource, workManager,
dispatcherIO
) { ) {
override suspend fun updateArticleInfo(article: Article) { override suspend fun updateArticleInfo(article: Article) {
articleDao.update(article) articleDao.update(article)
@ -58,6 +73,7 @@ class FeverRssRepository @Inject constructor(
} }
override suspend fun sync() { override suspend fun sync() {
applicationScope.launch(dispatcherDefault) {
mutex.withLock { mutex.withLock {
val accountId = context.currentAccountId val accountId = context.currentAccountId
@ -143,4 +159,5 @@ class FeverRssRepository @Inject constructor(
} }
} }
} }
}
} }

View File

@ -11,11 +11,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import androidx.work.WorkManager import androidx.work.WorkManager
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import me.ash.reader.MainActivity import me.ash.reader.MainActivity
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.currentAccountId import me.ash.reader.currentAccountId
@ -26,6 +22,9 @@ import me.ash.reader.data.feed.Feed
import me.ash.reader.data.feed.FeedDao import me.ash.reader.data.feed.FeedDao
import me.ash.reader.data.group.Group import me.ash.reader.data.group.Group
import me.ash.reader.data.group.GroupDao import me.ash.reader.data.group.GroupDao
import me.ash.reader.data.module.ApplicationScope
import me.ash.reader.data.module.DispatcherDefault
import me.ash.reader.data.module.DispatcherIO
import me.ash.reader.data.source.RssNetworkDataSource import me.ash.reader.data.source.RssNetworkDataSource
import me.ash.reader.ui.page.common.ExtraName import me.ash.reader.ui.page.common.ExtraName
import me.ash.reader.ui.page.common.NotificationGroupName import me.ash.reader.ui.page.common.NotificationGroupName
@ -41,10 +40,17 @@ class LocalRssRepository @Inject constructor(
private val rssNetworkDataSource: RssNetworkDataSource, private val rssNetworkDataSource: RssNetworkDataSource,
private val accountDao: AccountDao, private val accountDao: AccountDao,
private val groupDao: GroupDao, private val groupDao: GroupDao,
@ApplicationScope
private val applicationScope: CoroutineScope,
@DispatcherDefault
private val dispatcherDefault: CoroutineDispatcher,
@DispatcherIO
private val dispatcherIO: CoroutineDispatcher,
workManager: WorkManager, workManager: WorkManager,
) : AbstractRssRepository( ) : AbstractRssRepository(
context, accountDao, articleDao, groupDao, context, accountDao, articleDao, groupDao,
feedDao, rssNetworkDataSource, workManager, feedDao, rssNetworkDataSource, workManager,
dispatcherIO
) { ) {
private val notificationManager: NotificationManager = private val notificationManager: NotificationManager =
(getSystemService( (getSystemService(
@ -84,8 +90,7 @@ class LocalRssRepository @Inject constructor(
} }
override suspend fun sync() { override suspend fun sync() {
mutex.withLock { applicationScope.launch(dispatcherDefault) {
withContext(Dispatchers.IO) {
val preTime = System.currentTimeMillis() val preTime = System.currentTimeMillis()
val accountId = context.currentAccountId val accountId = context.currentAccountId
val articles = mutableListOf<Article>() val articles = mutableListOf<Article>()
@ -116,8 +121,7 @@ class LocalRssRepository @Inject constructor(
currentFeedName = "" currentFeedName = ""
) )
} }
} }.join()
}
} }
data class ArticleNotify( data class ArticleNotify(
@ -127,10 +131,20 @@ class LocalRssRepository @Inject constructor(
private suspend fun syncFeed(feed: Feed): ArticleNotify { private suspend fun syncFeed(feed: Feed): ArticleNotify {
val latest = articleDao.queryLatestByFeedId(context.currentAccountId, feed.id) val latest = articleDao.queryLatestByFeedId(context.currentAccountId, feed.id)
val articles = rssHelper.queryRssXml(feed, latest?.link).also { var articles: List<Article>? = null
if (feed.icon == null && it.isNotEmpty()) { try {
rssHelper.queryRssIcon(feedDao, feed, it.first().link) articles = rssHelper.queryRssXml(feed, latest?.link)
} catch (e: Exception) {
Log.e("RLog", "queryRssXml[${feed.name}]: ${e.message}")
return ArticleNotify(listOf(), false)
} }
try {
if (feed.icon == null && !articles.isNullOrEmpty()) {
rssHelper.queryRssIcon(feedDao, feed, articles.first().link)
}
} catch (e: Exception) {
Log.e("RLog", "queryRssIcon[${feed.name}]: ${e.message}")
return ArticleNotify(listOf(), false)
} }
updateSyncState { updateSyncState {
it.copy( it.copy(

View File

@ -1,19 +1,20 @@
package me.ash.reader.data.repository package me.ash.reader.data.repository
import android.content.Context import android.content.Context
import android.util.Log
import be.ceau.opml.OpmlWriter import be.ceau.opml.OpmlWriter
import be.ceau.opml.entity.Body import be.ceau.opml.entity.Body
import be.ceau.opml.entity.Head import be.ceau.opml.entity.Head
import be.ceau.opml.entity.Opml import be.ceau.opml.entity.Opml
import be.ceau.opml.entity.Outline import be.ceau.opml.entity.Outline
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import me.ash.reader.R
import me.ash.reader.currentAccountId import me.ash.reader.currentAccountId
import me.ash.reader.data.account.AccountDao import me.ash.reader.data.account.AccountDao
import me.ash.reader.data.feed.Feed import me.ash.reader.data.feed.Feed
import me.ash.reader.data.feed.FeedDao import me.ash.reader.data.feed.FeedDao
import me.ash.reader.data.group.GroupDao import me.ash.reader.data.group.GroupDao
import me.ash.reader.data.source.OpmlLocalDataSource import me.ash.reader.data.source.OpmlLocalDataSource
import me.ash.reader.spacerDollar
import java.io.InputStream import java.io.InputStream
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -25,11 +26,12 @@ class OpmlRepository @Inject constructor(
private val feedDao: FeedDao, private val feedDao: FeedDao,
private val accountDao: AccountDao, private val accountDao: AccountDao,
private val rssRepository: RssRepository, private val rssRepository: RssRepository,
private val opmlLocalDataSource: OpmlLocalDataSource private val opmlLocalDataSource: OpmlLocalDataSource,
private val stringsRepository: StringsRepository,
) { ) {
@Throws(Exception::class)
suspend fun saveToDatabase(inputStream: InputStream) { suspend fun saveToDatabase(inputStream: InputStream) {
try { val defaultGroup = groupDao.queryById(getDefaultGroupId())!!
val defaultGroup = groupDao.queryById(opmlLocalDataSource.getDefaultGroupId())!!
val groupWithFeedList = val groupWithFeedList =
opmlLocalDataSource.parseFileInputStream(inputStream, defaultGroup) opmlLocalDataSource.parseFileInputStream(inputStream, defaultGroup)
groupWithFeedList.forEach { groupWithFeed -> groupWithFeedList.forEach { groupWithFeed ->
@ -39,21 +41,18 @@ class OpmlRepository @Inject constructor(
val repeatList = mutableListOf<Feed>() val repeatList = mutableListOf<Feed>()
groupWithFeed.feeds.forEach { groupWithFeed.feeds.forEach {
it.groupId = groupWithFeed.group.id it.groupId = groupWithFeed.group.id
if (rssRepository.get().isExist(it.url)) { if (rssRepository.get().isFeedExist(it.url)) {
repeatList.add(it) repeatList.add(it)
} }
} }
feedDao.insertList((groupWithFeed.feeds subtract repeatList).toList()) feedDao.insertList((groupWithFeed.feeds subtract repeatList).toList())
} }
} catch (e: Exception) {
Log.e("saveToDatabase", "${e.message}")
}
} }
suspend fun saveToString(): String? = @Throws(Exception::class)
try { suspend fun saveToString(): String {
val defaultGroup = groupDao.queryById(opmlLocalDataSource.getDefaultGroupId())!! val defaultGroup = groupDao.queryById(getDefaultGroupId())!!
OpmlWriter().write( return OpmlWriter().write(
Opml( Opml(
"2.0", "2.0",
Head( Head(
@ -85,9 +84,12 @@ class OpmlRepository @Inject constructor(
) )
}) })
) )
) )!!
} catch (e: Exception) { }
Log.e("saveToString", "${e.message}")
null private fun getDefaultGroupId(): String {
val readYouString = stringsRepository.getString(R.string.read_you)
val defaultString = stringsRepository.getString(R.string.defaults)
return context.currentAccountId.spacerDollar(readYouString + defaultString)
} }
} }

View File

@ -4,17 +4,20 @@ import android.content.Context
import android.text.Html import android.text.Html
import android.util.Log import android.util.Log
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import me.ash.reader.currentAccountId import me.ash.reader.currentAccountId
import me.ash.reader.data.article.Article import me.ash.reader.data.article.Article
import me.ash.reader.data.feed.Feed import me.ash.reader.data.feed.Feed
import me.ash.reader.data.feed.FeedDao import me.ash.reader.data.feed.FeedDao
import me.ash.reader.data.feed.FeedWithArticle import me.ash.reader.data.feed.FeedWithArticle
import me.ash.reader.data.module.DispatcherIO
import me.ash.reader.data.source.RssNetworkDataSource import me.ash.reader.data.source.RssNetworkDataSource
import me.ash.reader.spacerDollar import me.ash.reader.spacerDollar
import net.dankito.readability4j.Readability4J import net.dankito.readability4j.Readability4J
import net.dankito.readability4j.extended.Readability4JExtended import net.dankito.readability4j.extended.Readability4JExtended
import okhttp3.* import okhttp3.OkHttpClient
import java.io.IOException import okhttp3.Request
import java.text.ParsePosition import java.text.ParsePosition
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -24,9 +27,12 @@ class RssHelper @Inject constructor(
@ApplicationContext @ApplicationContext
private val context: Context, private val context: Context,
private val rssNetworkDataSource: RssNetworkDataSource, private val rssNetworkDataSource: RssNetworkDataSource,
@DispatcherIO
private val dispatcherIO: CoroutineDispatcher,
) { ) {
@Throws(Exception::class) @Throws(Exception::class)
suspend fun searchFeed(feedLink: String): FeedWithArticle { suspend fun searchFeed(feedLink: String): FeedWithArticle {
return withContext(dispatcherIO) {
val accountId = context.currentAccountId val accountId = context.currentAccountId
val parseRss = rssNetworkDataSource.parseRss(feedLink) val parseRss = rssNetworkDataSource.parseRss(feedLink)
val feed = Feed( val feed = Feed(
@ -36,7 +42,8 @@ class RssHelper @Inject constructor(
groupId = "", groupId = "",
accountId = accountId, accountId = accountId,
) )
return FeedWithArticle(feed, queryRssXml(feed)) FeedWithArticle(feed, queryRssXml(feed))
}
} }
fun parseDescriptionContent(link: String, content: String): String { fun parseDescriptionContent(link: String, content: String): String {
@ -46,42 +53,39 @@ class RssHelper @Inject constructor(
return element.toString() return element.toString()
} }
fun parseFullContent(link: String, title: String, callback: (String) -> Unit) { @Throws(Exception::class)
OkHttpClient() suspend fun parseFullContent(link: String, title: String): String {
return withContext(dispatcherIO) {
val response = OkHttpClient()
.newCall(Request.Builder().url(link).build()) .newCall(Request.Builder().url(link).build())
.enqueue(object : Callback { .execute()
override fun onFailure(call: Call, e: IOException) { val content = response.body!!.string()
callback(e.message.toString())
}
override fun onResponse(call: Call, response: Response) {
val content = response.body?.string()
val readability4J: Readability4J = val readability4J: Readability4J =
Readability4JExtended(link, content ?: "") Readability4JExtended(link, content)
val articleContent = readability4J.parse().articleContent val articleContent = readability4J.parse().articleContent
if (articleContent == null) { if (articleContent == null) {
callback("") ""
} else { } else {
val h1Element = articleContent.selectFirst("h1") val h1Element = articleContent.selectFirst("h1")
if (h1Element != null && h1Element.hasText() && h1Element.text() == title) { if (h1Element != null && h1Element.hasText() && h1Element.text() == title) {
h1Element.remove() h1Element.remove()
} }
callback(articleContent.toString()) articleContent.toString()
} }
} }
})
} }
@Throws(Exception::class)
suspend fun queryRssXml( suspend fun queryRssXml(
feed: Feed, feed: Feed,
latestLink: String? = null, latestLink: String? = null,
): List<Article> { ): List<Article> {
return withContext(dispatcherIO) {
val a = mutableListOf<Article>() val a = mutableListOf<Article>()
try {
val accountId = context.currentAccountId val accountId = context.currentAccountId
val parseRss = rssNetworkDataSource.parseRss(feed.url) val parseRss = rssNetworkDataSource.parseRss(feed.url)
parseRss.items.forEach { parseRss.items.forEach {
if (latestLink != null && latestLink == it.link) return a if (latestLink != null && latestLink == it.link) return@withContext a
Log.i("RLog", "request rss ${feed.name}: ${it.title}") Log.i("RLog", "request rss ${feed.name}: ${it.title}")
a.add( a.add(
Article( Article(
@ -104,27 +108,23 @@ class RssHelper @Inject constructor(
) )
) )
} }
return a a
} catch (e: Exception) {
Log.e("RLog", "error ${feed.name}: ${e.message}")
return a
} }
} }
@Throws(Exception::class)
suspend fun queryRssIcon( suspend fun queryRssIcon(
feedDao: FeedDao, feedDao: FeedDao,
feed: Feed, feed: Feed,
articleLink: String?, articleLink: String,
) { ) {
try { withContext(dispatcherIO) {
if (articleLink == null) return
val execute = OkHttpClient() val execute = OkHttpClient()
.newCall(Request.Builder().url(articleLink).build()) .newCall(Request.Builder().url(articleLink).build())
.execute() .execute()
val content = execute.body?.string() val content = execute.body!!.string()
val regex = val regex =
Regex("""<link(.+?)rel="shortcut icon"(.+?)href="(.+?)"""") Regex("""<link(.+?)rel="shortcut icon"(.+?)href="(.+?)"""")
if (content != null) {
var iconLink = regex var iconLink = regex
.find(content) .find(content)
?.groups?.get(3) ?.groups?.get(3)
@ -144,24 +144,22 @@ class RssHelper @Inject constructor(
} else { } else {
// saveRssIcon(feedDao, feed, "") // saveRssIcon(feedDao, feed, "")
} }
} else {
// saveRssIcon(feedDao, feed, "")
}
} catch (e: Exception) {
Log.e("RLog", "queryRssIcon: ${e.message}")
} }
} }
private suspend fun saveRssIcon(feedDao: FeedDao, feed: Feed, iconLink: String) { @Throws(Exception::class)
val execute = OkHttpClient() suspend fun saveRssIcon(feedDao: FeedDao, feed: Feed, iconLink: String) {
withContext(dispatcherIO) {
val response = OkHttpClient()
.newCall(Request.Builder().url(iconLink).build()) .newCall(Request.Builder().url(iconLink).build())
.execute() .execute()
feedDao.update( feedDao.update(
feed.apply { feed.apply {
icon = execute.body?.bytes() icon = response.body!!.bytes()
} }
) )
} }
}
private fun parseDate( private fun parseDate(
inputDate: String, patterns: Array<String?> = arrayOf( inputDate: String, patterns: Array<String?> = arrayOf(

View File

@ -2,10 +2,8 @@ package me.ash.reader.data.repository
import android.content.Context import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import me.ash.reader.DataStoreKeys import me.ash.reader.currentAccountType
import me.ash.reader.data.account.Account import me.ash.reader.data.account.Account
import me.ash.reader.dataStore
import me.ash.reader.get
import javax.inject.Inject import javax.inject.Inject
class RssRepository @Inject constructor( class RssRepository @Inject constructor(
@ -15,13 +13,11 @@ class RssRepository @Inject constructor(
private val feverRssRepository: FeverRssRepository, private val feverRssRepository: FeverRssRepository,
// private val googleReaderRssRepository: GoogleReaderRssRepository, // private val googleReaderRssRepository: GoogleReaderRssRepository,
) { ) {
fun get() = when (getAccountType()) { fun get() = when (context.currentAccountType) {
Account.Type.LOCAL -> localRssRepository Account.Type.LOCAL -> localRssRepository
// Account.Type.LOCAL -> feverRssRepository // Account.Type.LOCAL -> feverRssRepository
Account.Type.FEVER -> feverRssRepository Account.Type.FEVER -> feverRssRepository
// Account.Type.GOOGLE_READER -> googleReaderRssRepository // Account.Type.GOOGLE_READER -> googleReaderRssRepository
else -> throw IllegalStateException("Unknown account type: ${getAccountType()}") else -> throw IllegalStateException("Unknown account type: ${context.currentAccountType}")
} }
private fun getAccountType(): Int = context.dataStore.get(DataStoreKeys.CurrentAccountType)!!
} }

View File

@ -3,11 +3,13 @@ package me.ash.reader.data.source
import android.content.Context import android.content.Context
import be.ceau.opml.OpmlParser import be.ceau.opml.OpmlParser
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import me.ash.reader.* import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import me.ash.reader.currentAccountId
import me.ash.reader.data.feed.Feed import me.ash.reader.data.feed.Feed
import me.ash.reader.data.group.Group import me.ash.reader.data.group.Group
import me.ash.reader.data.group.GroupWithFeed import me.ash.reader.data.group.GroupWithFeed
import me.ash.reader.data.repository.StringsRepository import me.ash.reader.data.module.DispatcherIO
import java.io.InputStream import java.io.InputStream
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -15,18 +17,15 @@ import javax.inject.Inject
class OpmlLocalDataSource @Inject constructor( class OpmlLocalDataSource @Inject constructor(
@ApplicationContext @ApplicationContext
private val context: Context, private val context: Context,
private val stringsRepository: StringsRepository, @DispatcherIO
private val dispatcherIO: CoroutineDispatcher,
) { ) {
fun getDefaultGroupId(): String { @Throws(Exception::class)
val readYouString = stringsRepository.getString(R.string.read_you) suspend fun parseFileInputStream(
val defaultString = stringsRepository.getString(R.string.defaults) inputStream: InputStream,
return context.dataStore defaultGroup: Group
.get(DataStoreKeys.CurrentAccountId)!! ): List<GroupWithFeed> {
.spacerDollar(readYouString + defaultString) return withContext(dispatcherIO) {
}
// @Throws(XmlPullParserException::class, IOException::class)
fun parseFileInputStream(inputStream: InputStream, defaultGroup: Group): List<GroupWithFeed> {
val accountId = context.currentAccountId val accountId = context.currentAccountId
val opml = OpmlParser().parse(inputStream) val opml = OpmlParser().parse(inputStream)
val groupWithFeedList = mutableListOf<GroupWithFeed>().also { val groupWithFeedList = mutableListOf<GroupWithFeed>().also {
@ -87,7 +86,8 @@ class OpmlLocalDataSource @Inject constructor(
} }
} }
} }
return groupWithFeedList groupWithFeedList
}
} }
private fun MutableList<GroupWithFeed>.addGroup(group: Group) { private fun MutableList<GroupWithFeed>.addGroup(group: Group) {

View File

@ -1,5 +1,6 @@
package me.ash.reader.ui.page.home.drawer.feed package me.ash.reader.ui.page.home.drawer.feed
import android.util.Log
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
@ -8,10 +9,10 @@ import androidx.compose.material.icons.rounded.DeleteOutline
import androidx.compose.material.icons.rounded.RssFeed import androidx.compose.material.icons.rounded.RssFeed
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@ -31,7 +32,6 @@ fun FeedOptionDrawer(
viewModel: FeedOptionViewModel = hiltViewModel(), viewModel: FeedOptionViewModel = hiltViewModel(),
content: @Composable () -> Unit = {}, content: @Composable () -> Unit = {},
) { ) {
val context = LocalContext.current
val viewState = viewModel.viewState.collectAsStateValue() val viewState = viewModel.viewState.collectAsStateValue()
val feed = viewState.feed val feed = viewState.feed

View File

@ -49,14 +49,22 @@ class FeedsViewModel @Inject constructor(
private fun importFromInputStream(inputStream: InputStream) { private fun importFromInputStream(inputStream: InputStream) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try {
opmlRepository.saveToDatabase(inputStream) opmlRepository.saveToDatabase(inputStream)
rssRepository.get().doSync() rssRepository.get().doSync()
} catch (e: Exception) {
Log.e("FeedsViewModel", "importFromInputStream: ", e)
}
} }
} }
private fun exportAsOpml(callback: (String) -> Unit = {}) { private fun exportAsOpml(callback: (String) -> Unit = {}) {
viewModelScope.launch(Dispatchers.Default) { viewModelScope.launch(Dispatchers.Default) {
opmlRepository.saveToString()?.let { callback(it) } try {
callback(opmlRepository.saveToString())
} catch (e: Exception) {
Log.e("FeedsViewModel", "exportAsOpml: ", e)
}
} }
} }
@ -74,7 +82,6 @@ class FeedsViewModel @Inject constructor(
rssRepository.get().pullFeeds(), rssRepository.get().pullFeeds(),
rssRepository.get().pullImportant(isStarred, isUnread), rssRepository.get().pullImportant(isStarred, isUnread),
) { groupWithFeedList, importantList -> ) { groupWithFeedList, importantList ->
Log.i("RLog", "thread:combine ${Thread.currentThread().name}")
val groupImportantMap = mutableMapOf<String, Int>() val groupImportantMap = mutableMapOf<String, Int>()
val feedImportantMap = mutableMapOf<String, Int>() val feedImportantMap = mutableMapOf<String, Int>()
importantList.groupBy { it.groupId }.forEach { (i, list) -> importantList.groupBy { it.groupId }.forEach { (i, list) ->
@ -109,8 +116,6 @@ class FeedsViewModel @Inject constructor(
}.onStart { }.onStart {
}.onEach { groupWithFeedList -> }.onEach { groupWithFeedList ->
Log.i("RLog", "thread:onEach ${Thread.currentThread().name}")
_viewState.update { _viewState.update {
it.copy( it.copy(
filter = when { filter = when {

View File

@ -148,7 +148,7 @@ class SubscribeViewModel @Inject constructor(
lockLinkInput = true, lockLinkInput = true,
) )
} }
if (rssRepository.get().isExist(_viewState.value.linkContent)) { if (rssRepository.get().isFeedExist(_viewState.value.linkContent)) {
_viewState.update { _viewState.update {
it.copy( it.copy(
title = stringsRepository.getString(R.string.subscribe), title = stringsRepository.getString(R.string.subscribe),

View File

@ -1,6 +1,5 @@
package me.ash.reader.ui.page.home.flow package me.ash.reader.ui.page.home.flow
import android.util.Log
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -55,7 +54,6 @@ class FlowViewModel @Inject constructor(
_viewState.update { _viewState.update {
it.copy( it.copy(
pagingData = Pager(PagingConfig(pageSize = 10)) { pagingData = Pager(PagingConfig(pageSize = 10)) {
Log.i("RLog", "thread:Pager ${Thread.currentThread().name}")
rssRepository.get().pullArticles( rssRepository.get().pullArticles(
groupId = filterState.group?.id, groupId = filterState.group?.id,
feedId = filterState.feed?.id, feedId = filterState.feed?.id,

View File

@ -1,5 +1,6 @@
package me.ash.reader.ui.page.home.read package me.ash.reader.ui.page.home.read
import android.util.Log
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -55,12 +56,23 @@ class ReadViewModel @Inject constructor(
private fun renderFullContent() { private fun renderFullContent() {
changeLoading(true) changeLoading(true)
rssHelper.parseFullContent( viewModelScope.launch {
try {
_viewState.update {
it.copy(
content = rssHelper.parseFullContent(
_viewState.value.articleWithFeed?.article?.link ?: "", _viewState.value.articleWithFeed?.article?.link ?: "",
_viewState.value.articleWithFeed?.article?.title ?: "" _viewState.value.articleWithFeed?.article?.title ?: ""
) { content -> )
)
}
} catch (e: Exception) {
Log.i("RLog", "renderFullContent: ${e.message}")
_viewState.update { _viewState.update {
it.copy(content = content) it.copy(
content = e.message
)
}
} }
} }
} }