mirror of
https://github.com/readrops/Readrops.git
synced 2025-02-03 03:57:36 +01:00
Add local account refresh logic in SyncWorker
This commit is contained in:
parent
f95c808aa0
commit
5a2ef0fa2f
@ -3,8 +3,8 @@ package com.readrops.app.repositories
|
||||
import com.readrops.api.services.Credentials
|
||||
import com.readrops.api.services.SyncResult
|
||||
import com.readrops.api.services.SyncType
|
||||
import com.readrops.api.services.freshrss.FreshRSSSyncData
|
||||
import com.readrops.api.services.freshrss.FreshRSSDataSource
|
||||
import com.readrops.api.services.freshrss.FreshRSSSyncData
|
||||
import com.readrops.api.utils.AuthInterceptor
|
||||
import com.readrops.app.util.Utils
|
||||
import com.readrops.db.Database
|
||||
@ -38,7 +38,7 @@ class FreshRSSRepository(
|
||||
|
||||
override suspend fun synchronize(
|
||||
selectedFeeds: List<Feed>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
onUpdate: suspend (Feed) -> Unit
|
||||
): Pair<SyncResult, ErrorResult> = throw NotImplementedError("This method can't be called here")
|
||||
|
||||
override suspend fun synchronize(): SyncResult {
|
||||
@ -86,7 +86,7 @@ class FreshRSSRepository(
|
||||
newFeeds: List<Feed>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
): ErrorResult {
|
||||
val errors = mutableMapOf<Feed, Exception>()
|
||||
val errors = hashMapOf<Feed, Exception>()
|
||||
|
||||
for (newFeed in newFeeds) {
|
||||
onUpdate(newFeed)
|
||||
|
@ -29,9 +29,9 @@ class LocalRSSRepository(
|
||||
|
||||
override suspend fun synchronize(
|
||||
selectedFeeds: List<Feed>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
onUpdate: suspend (Feed) -> Unit
|
||||
): Pair<SyncResult, ErrorResult> {
|
||||
val errors = mutableMapOf<Feed, Exception>()
|
||||
val errors = hashMapOf<Feed, Exception>()
|
||||
val syncResult = SyncResult()
|
||||
|
||||
val feeds = selectedFeeds.ifEmpty {
|
||||
@ -73,7 +73,7 @@ class LocalRSSRepository(
|
||||
newFeeds: List<Feed>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
): ErrorResult = withContext(Dispatchers.IO) {
|
||||
val errors = mutableMapOf<Feed, Exception>()
|
||||
val errors = hashMapOf<Feed, Exception>()
|
||||
|
||||
for (newFeed in newFeeds) {
|
||||
onUpdate(newFeed)
|
||||
|
@ -39,7 +39,7 @@ class NextcloudNewsRepository(
|
||||
|
||||
override suspend fun synchronize(
|
||||
selectedFeeds: List<Feed>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
onUpdate: suspend (Feed) -> Unit
|
||||
): Pair<SyncResult, ErrorResult> = throw NotImplementedError("This method can't be called here")
|
||||
|
||||
override suspend fun synchronize(): SyncResult {
|
||||
@ -85,7 +85,7 @@ class NextcloudNewsRepository(
|
||||
newFeeds: List<Feed>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
): ErrorResult {
|
||||
val errors = mutableMapOf<Feed, Exception>()
|
||||
val errors = hashMapOf<Feed, Exception>()
|
||||
|
||||
for (newFeed in newFeeds) {
|
||||
onUpdate(newFeed)
|
||||
|
@ -8,7 +8,7 @@ import com.readrops.db.entities.Item
|
||||
import com.readrops.db.entities.ItemState
|
||||
import com.readrops.db.entities.account.Account
|
||||
|
||||
typealias ErrorResult = Map<Feed, Exception>
|
||||
typealias ErrorResult = HashMap<Feed, Exception>
|
||||
|
||||
interface Repository {
|
||||
|
||||
@ -26,7 +26,7 @@ interface Repository {
|
||||
*/
|
||||
suspend fun synchronize(
|
||||
selectedFeeds: List<Feed>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
onUpdate: suspend (Feed) -> Unit
|
||||
): Pair<SyncResult, ErrorResult>
|
||||
|
||||
/**
|
||||
@ -206,7 +206,7 @@ abstract class BaseRepository(
|
||||
foldersAndFeeds: Map<Folder?, List<Feed>>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
): ErrorResult {
|
||||
val errors = mutableMapOf<Feed, Exception>()
|
||||
val errors = hashMapOf<Feed, Exception>()
|
||||
|
||||
for ((folder, feeds) in foldersAndFeeds) {
|
||||
if (folder != null) {
|
||||
|
@ -7,6 +7,7 @@ import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
@ -16,8 +17,11 @@ import com.readrops.api.services.SyncResult
|
||||
import com.readrops.app.R
|
||||
import com.readrops.app.ReadropsApp
|
||||
import com.readrops.app.repositories.BaseRepository
|
||||
import com.readrops.app.repositories.ErrorResult
|
||||
import com.readrops.app.util.FeedColors
|
||||
import com.readrops.app.util.putSerializable
|
||||
import com.readrops.db.Database
|
||||
import com.readrops.db.entities.account.Account
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
@ -34,7 +38,7 @@ class SyncWorker(
|
||||
@SuppressLint("MissingPermission")
|
||||
override suspend fun doWork(): Result {
|
||||
val sharedPreferences = get<SharedPreferences>()
|
||||
var result = Result.success(workDataOf(END_SYNC_KEY to true))
|
||||
var workResult = Result.success(workDataOf(END_SYNC_KEY to true))
|
||||
|
||||
try {
|
||||
require(notificationManager.areNotificationsEnabled())
|
||||
@ -47,11 +51,18 @@ class SyncWorker(
|
||||
.setStyle(NotificationCompat.BigTextStyle())
|
||||
.setOnlyAlertOnce(true)
|
||||
|
||||
val accounts = database.accountDao().selectAllAccounts().first()
|
||||
val accountId = inputData.getInt(ACCOUNT_ID_KEY, 0)
|
||||
val accounts = if (accountId == 0) {
|
||||
database.accountDao().selectAllAccounts().first()
|
||||
} else {
|
||||
listOf(database.accountDao().select(accountId))
|
||||
}
|
||||
|
||||
for (account in accounts) {
|
||||
account.login = sharedPreferences.getString(account.loginKey, null)
|
||||
account.password = sharedPreferences.getString(account.passwordKey, null)
|
||||
if (!account.isLocal) {
|
||||
account.login = sharedPreferences.getString(account.loginKey, null)
|
||||
account.password = sharedPreferences.getString(account.passwordKey, null)
|
||||
}
|
||||
|
||||
val repository = get<BaseRepository> { parametersOf(account) }
|
||||
|
||||
@ -63,21 +74,81 @@ class SyncWorker(
|
||||
)
|
||||
notificationManager.notify(SYNC_NOTIFICATION_ID, notificationBuilder.build())
|
||||
|
||||
val syncResult = repository.synchronize()
|
||||
fetchFeedColors(syncResult, notificationBuilder)
|
||||
if (account.isLocal) {
|
||||
val result = refreshLocalAccount(repository, account)
|
||||
|
||||
if (result.second.isNotEmpty()) {
|
||||
workResult = Result.success(
|
||||
workDataOf(END_SYNC_KEY to true)
|
||||
.putSerializable(LOCAL_SYNC_ERRORS_KEY, result.second)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val syncResult = repository.synchronize()
|
||||
fetchFeedColors(syncResult, notificationBuilder)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "${e.message}")
|
||||
result = Result.failure(workDataOf(SYNC_FAILURE_KEY to true))
|
||||
workResult = Result.failure(
|
||||
workDataOf(SYNC_FAILURE_KEY to true)
|
||||
.putSerializable(SYNC_FAILURE_EXCEPTION_KEY, e)
|
||||
)
|
||||
} finally {
|
||||
notificationManager.cancel(SYNC_NOTIFICATION_ID)
|
||||
}
|
||||
|
||||
return workResult
|
||||
}
|
||||
|
||||
private suspend fun refreshLocalAccount(
|
||||
repository: BaseRepository,
|
||||
account: Account
|
||||
): Pair<SyncResult, ErrorResult> {
|
||||
val feedId = inputData.getInt(FEED_ID_KEY, 0)
|
||||
val folderId = inputData.getInt(FOLDER_ID_KEY, 0)
|
||||
|
||||
val feeds = when {
|
||||
feedId > 0 -> listOf(database.feedDao().selectFeed(feedId))
|
||||
folderId > 0 -> database.feedDao().selectFeedsByFolder(folderId)
|
||||
else -> listOf()
|
||||
}
|
||||
|
||||
var feedCount = 0
|
||||
val feedMax = if (feeds.isNotEmpty()) {
|
||||
feeds.size
|
||||
} else {
|
||||
database.feedDao().selectFeedCount(account.id)
|
||||
}
|
||||
|
||||
val result = repository.synchronize(
|
||||
selectedFeeds = feeds,
|
||||
onUpdate = { feed ->
|
||||
setProgress(
|
||||
workDataOf(
|
||||
FEED_NAME_KEY to feed.name,
|
||||
FEED_MAX_KEY to feedMax,
|
||||
FEED_COUNT_KEY to ++feedCount
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (result.second.isNotEmpty()) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"refreshing local account ${account.accountName}: ${result.second.size} errors"
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private suspend fun fetchFeedColors(syncResult: SyncResult, notificationBuilder: NotificationCompat.Builder) {
|
||||
private suspend fun fetchFeedColors(
|
||||
syncResult: SyncResult,
|
||||
notificationBuilder: NotificationCompat.Builder
|
||||
) {
|
||||
notificationBuilder.setContentTitle(applicationContext.getString(R.string.get_feeds_colors))
|
||||
|
||||
for ((index, feedId) in syncResult.newFeedIds.withIndex()) {
|
||||
@ -88,7 +159,8 @@ class SyncWorker(
|
||||
notificationManager.notify(SYNC_NOTIFICATION_ID, notificationBuilder.build())
|
||||
|
||||
try {
|
||||
val color = FeedColors.getFeedColor(syncResult.feeds.first { it.id == feedId.toInt() }.iconUrl!!)
|
||||
val color =
|
||||
FeedColors.getFeedColor(syncResult.feeds.first { it.id == feedId.toInt() }.iconUrl!!)
|
||||
database.feedDao().updateFeedColor(feedId.toInt(), color)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "$feedName: ${e.message}")
|
||||
@ -104,10 +176,19 @@ class SyncWorker(
|
||||
|
||||
const val END_SYNC_KEY = "END_SYNC"
|
||||
const val SYNC_FAILURE_KEY = "SYNC_FAILURE"
|
||||
const val SYNC_FAILURE_EXCEPTION_KEY = "SYNC_FAILURE_EXCEPTION"
|
||||
const val ACCOUNT_ID_KEY = "ACCOUNT_ID"
|
||||
const val FEED_ID_KEY = "FEED_ID"
|
||||
const val FOLDER_ID_KEY = "FOLDER_ID"
|
||||
const val FEED_NAME_KEY = "FEED_NAME"
|
||||
const val FEED_MAX_KEY = "FEED_MAX"
|
||||
const val FEED_COUNT_KEY = "FEED_COUNT"
|
||||
const val LOCAL_SYNC_ERRORS_KEY = "LOCAL_SYNC_ERRORS"
|
||||
|
||||
suspend fun startNow(context: Context, onUpdate: (WorkInfo) -> Unit) {
|
||||
suspend fun startNow(context: Context, data: Data, onUpdate: (WorkInfo) -> Unit) {
|
||||
val request = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||
.addTag(TAG)
|
||||
.setInputData(data)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).apply {
|
||||
|
@ -7,12 +7,14 @@ import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.work.workDataOf
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.readrops.app.base.TabScreenModel
|
||||
import com.readrops.app.repositories.ErrorResult
|
||||
import com.readrops.app.repositories.GetFoldersWithFeeds
|
||||
import com.readrops.app.sync.SyncWorker
|
||||
import com.readrops.app.util.Preferences
|
||||
import com.readrops.app.util.getSerializable
|
||||
import com.readrops.db.Database
|
||||
import com.readrops.db.entities.Feed
|
||||
import com.readrops.db.entities.Folder
|
||||
@ -141,31 +143,64 @@ class TimelineScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun refreshTimeline(context: Context) {
|
||||
screenModelScope.launch(dispatcher) {
|
||||
if (currentAccount!!.isLocal) {
|
||||
refreshLocalAccount()
|
||||
val filterPair = with(filters.value) {
|
||||
when (subFilter) {
|
||||
SubFilter.FEED -> SyncWorker.FEED_ID_KEY to filterFeedId
|
||||
SubFilter.FOLDER -> SyncWorker.FOLDER_ID_KEY to filterFolderId
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
val accountPair = SyncWorker.ACCOUNT_ID_KEY to currentAccount!!.id
|
||||
|
||||
val workData = if (filterPair != null) {
|
||||
workDataOf(filterPair, accountPair)
|
||||
} else {
|
||||
_timelineState.update { it.copy(isRefreshing = true) }
|
||||
workDataOf(accountPair)
|
||||
}
|
||||
|
||||
SyncWorker.startNow(context) { data ->
|
||||
when {
|
||||
data.outputData.getBoolean(SyncWorker.END_SYNC_KEY, false) -> {
|
||||
_timelineState.update {
|
||||
it.copy(
|
||||
isRefreshing = false,
|
||||
scrollToTop = true
|
||||
)
|
||||
}
|
||||
if (!currentAccount!!.isLocal) {
|
||||
_timelineState.update {
|
||||
it.copy(isRefreshing = true)
|
||||
}
|
||||
}
|
||||
|
||||
SyncWorker.startNow(context, workData) { workInfo ->
|
||||
when {
|
||||
workInfo.outputData.getBoolean(SyncWorker.END_SYNC_KEY, false) -> {
|
||||
val errors = workInfo.outputData.getSerializable(SyncWorker.LOCAL_SYNC_ERRORS_KEY) as ErrorResult?
|
||||
|
||||
_timelineState.update {
|
||||
it.copy(
|
||||
isRefreshing = false,
|
||||
hideReadAllFAB = false,
|
||||
scrollToTop = true,
|
||||
localSyncErrors = errors?.ifEmpty { null }
|
||||
)
|
||||
}
|
||||
}
|
||||
workInfo.outputData.getBoolean(SyncWorker.SYNC_FAILURE_KEY, false) -> {
|
||||
val error = workInfo.outputData.getSerializable(SyncWorker.SYNC_FAILURE_EXCEPTION_KEY) as Exception?
|
||||
|
||||
data.outputData.getBoolean(SyncWorker.SYNC_FAILURE_KEY, false) -> {
|
||||
_timelineState.update {
|
||||
it.copy(
|
||||
syncError = Exception(), // TODO see how to improve this
|
||||
isRefreshing = false
|
||||
)
|
||||
}
|
||||
_timelineState.update {
|
||||
it.copy(
|
||||
syncError = error,
|
||||
isRefreshing = false,
|
||||
hideReadAllFAB = false
|
||||
)
|
||||
}
|
||||
}
|
||||
workInfo.progress.getString(SyncWorker.FEED_NAME_KEY) != null -> {
|
||||
_timelineState.update {
|
||||
it.copy(
|
||||
isRefreshing = true,
|
||||
hideReadAllFAB = true,
|
||||
currentFeed = workInfo.progress.getString(SyncWorker.FEED_NAME_KEY) ?: "",
|
||||
feedCount = workInfo.progress.getInt(SyncWorker.FEED_COUNT_KEY, 0),
|
||||
feedMax = workInfo.progress.getInt(SyncWorker.FEED_MAX_KEY, 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -173,53 +208,6 @@ class TimelineScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshLocalAccount() {
|
||||
val selectedFeeds = when (filters.value.subFilter) {
|
||||
SubFilter.FEED -> listOf(
|
||||
database.feedDao().selectFeed(filters.value.filterFeedId)
|
||||
)
|
||||
|
||||
SubFilter.FOLDER -> database.feedDao()
|
||||
.selectFeedsByFolder(filters.value.filterFolderId)
|
||||
|
||||
else -> listOf()
|
||||
}
|
||||
|
||||
|
||||
_timelineState.update {
|
||||
it.copy(
|
||||
feedCount = 0,
|
||||
feedMax = if (selectedFeeds.isNotEmpty())
|
||||
selectedFeeds.size
|
||||
else
|
||||
database.feedDao().selectFeedCount(currentAccount!!.id)
|
||||
)
|
||||
}
|
||||
|
||||
_timelineState.update { it.copy(isRefreshing = true, hideReadAllFAB = true) }
|
||||
|
||||
val results = repository?.synchronize(
|
||||
selectedFeeds = selectedFeeds,
|
||||
onUpdate = { feed ->
|
||||
_timelineState.update {
|
||||
it.copy(
|
||||
currentFeed = feed.name!!,
|
||||
feedCount = it.feedCount + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
_timelineState.update {
|
||||
it.copy(
|
||||
isRefreshing = false,
|
||||
scrollToTop = true,
|
||||
hideReadAllFAB = false,
|
||||
localSyncErrors = if (results!!.second.isNotEmpty()) results.second else null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun openDrawer() {
|
||||
_timelineState.update { it.copy(isDrawerOpen = true) }
|
||||
}
|
||||
|
@ -6,7 +6,20 @@ import android.net.Uri
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.work.Data
|
||||
import java.io.Serializable
|
||||
|
||||
fun TextStyle.toDp(): Dp = fontSize.value.dp
|
||||
|
||||
fun Context.openUrl(url: String) = startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
fun Context.openUrl(url: String) = startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
|
||||
val Data.serializables by lazy {
|
||||
mutableMapOf<String, Serializable>()
|
||||
}
|
||||
|
||||
fun Data.putSerializable(key: String, parcelable: Serializable): Data {
|
||||
serializables[key] = parcelable
|
||||
return this
|
||||
}
|
||||
|
||||
fun Data.getSerializable(key: String): Serializable? = serializables[key]
|
||||
|
@ -18,6 +18,9 @@ interface AccountDao : BaseDao<Account> {
|
||||
@Insert
|
||||
suspend fun insertAccount(entity: Account): Long
|
||||
|
||||
@Query("Select * From Account Where id = :accountId")
|
||||
suspend fun select(accountId: Int): Account
|
||||
|
||||
@Query("Select * From Account")
|
||||
fun selectAllAccounts(): Flow<List<Account>>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user