mirror of https://github.com/readrops/Readrops.git
Add FreshRSS item read/state synchronization
This commit is contained in:
parent
0fbaec1263
commit
f9a2eb5e2c
|
@ -273,17 +273,17 @@ public class FreshRSSDataSource {
|
|||
*/
|
||||
private Completable setItemsReadState(@NonNull FreshRSSSyncData syncData, @NonNull String token) {
|
||||
Completable readItemsCompletable;
|
||||
if (syncData.getReadItemsIds().isEmpty()) {
|
||||
if (syncData.getReadIds().isEmpty()) {
|
||||
readItemsCompletable = Completable.complete();
|
||||
} else {
|
||||
readItemsCompletable = setItemsReadState(true, syncData.getReadItemsIds(), token);
|
||||
readItemsCompletable = setItemsReadState(true, syncData.getReadIds(), token);
|
||||
}
|
||||
|
||||
Completable unreadItemsCompletable;
|
||||
if (syncData.getUnreadItemsIds().isEmpty()) {
|
||||
if (syncData.getUnreadIds().isEmpty()) {
|
||||
unreadItemsCompletable = Completable.complete();
|
||||
} else {
|
||||
unreadItemsCompletable = setItemsReadState(false, syncData.getUnreadItemsIds(), token);
|
||||
unreadItemsCompletable = setItemsReadState(false, syncData.getUnreadIds(), token);
|
||||
}
|
||||
|
||||
return readItemsCompletable.concatWith(unreadItemsCompletable);
|
||||
|
@ -298,17 +298,17 @@ public class FreshRSSDataSource {
|
|||
*/
|
||||
private Completable setItemsStarState(@NonNull FreshRSSSyncData syncData, @NonNull String token) {
|
||||
Completable starredItemsCompletable;
|
||||
if (syncData.getStarredItemsIds().isEmpty()) {
|
||||
if (syncData.getStarredIds().isEmpty()) {
|
||||
starredItemsCompletable = Completable.complete();
|
||||
} else {
|
||||
starredItemsCompletable = setItemsStarState(true, syncData.getStarredItemsIds(), token);
|
||||
starredItemsCompletable = setItemsStarState(true, syncData.getStarredIds(), token);
|
||||
}
|
||||
|
||||
Completable unstarredItemsCompletable;
|
||||
if (syncData.getUnstarredItemsIds().isEmpty()) {
|
||||
if (syncData.getUnstarredIds().isEmpty()) {
|
||||
unstarredItemsCompletable = Completable.complete();
|
||||
} else {
|
||||
unstarredItemsCompletable = setItemsStarState(false, syncData.getUnstarredItemsIds(), token);
|
||||
unstarredItemsCompletable = setItemsStarState(false, syncData.getUnstarredIds(), token);
|
||||
}
|
||||
|
||||
return starredItemsCompletable.concatWith(unstarredItemsCompletable);
|
||||
|
|
|
@ -2,8 +2,8 @@ package com.readrops.api.services.freshrss
|
|||
|
||||
data class FreshRSSSyncData(
|
||||
var lastModified: Long = 0,
|
||||
var readItemsIds: List<String> = listOf(),
|
||||
var unreadItemsIds: List<String> = listOf(),
|
||||
var starredItemsIds: List<String> = listOf(),
|
||||
var unstarredItemsIds: List<String> = listOf(),
|
||||
var readIds: List<String> = listOf(),
|
||||
var unreadIds: List<String> = listOf(),
|
||||
var starredIds: List<String> = listOf(),
|
||||
var unstarredIds: List<String> = listOf(),
|
||||
)
|
|
@ -121,22 +121,22 @@ class NewFreshRSSDataSource(private val service: NewFreshRSSService) {
|
|||
}
|
||||
|
||||
private suspend fun setItemsReadState(syncData: FreshRSSSyncData, token: String) {
|
||||
if (syncData.readItemsIds.isNotEmpty()) {
|
||||
setItemsReadState(true, syncData.readItemsIds, token)
|
||||
if (syncData.readIds.isNotEmpty()) {
|
||||
setItemsReadState(true, syncData.readIds, token)
|
||||
}
|
||||
|
||||
if (syncData.unreadItemsIds.isNotEmpty()) {
|
||||
setItemsReadState(false, syncData.unreadItemsIds, token)
|
||||
if (syncData.unreadIds.isNotEmpty()) {
|
||||
setItemsReadState(false, syncData.unreadIds, token)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setItemsStarState(syncData: FreshRSSSyncData, token: String) {
|
||||
if (syncData.starredItemsIds.isNotEmpty()) {
|
||||
setItemStarState(true, syncData.starredItemsIds, token)
|
||||
if (syncData.starredIds.isNotEmpty()) {
|
||||
setItemStarState(true, syncData.starredIds, token)
|
||||
}
|
||||
|
||||
if (syncData.unstarredItemsIds.isNotEmpty()) {
|
||||
setItemStarState(false, syncData.unstarredItemsIds, token)
|
||||
if (syncData.unstarredIds.isNotEmpty()) {
|
||||
setItemStarState(false, syncData.unstarredIds, token)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -96,22 +96,22 @@ public class FreshRSSRepository extends ARepository {
|
|||
.itemStateChangesDao()
|
||||
.getItemStateChanges(account.getId());
|
||||
|
||||
syncData.setReadItemsIds(itemStateChanges.stream()
|
||||
syncData.setReadIds(itemStateChanges.stream()
|
||||
.filter(it -> it.getReadChange() && it.getRead())
|
||||
.map(ItemReadStarState::getRemoteId)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
syncData.setUnreadItemsIds(itemStateChanges.stream()
|
||||
syncData.setUnreadIds(itemStateChanges.stream()
|
||||
.filter(it -> it.getReadChange() && !it.getRead())
|
||||
.map(ItemReadStarState::getRemoteId)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
syncData.setStarredItemsIds(itemStateChanges.stream()
|
||||
syncData.setStarredIds(itemStateChanges.stream()
|
||||
.filter(it -> it.getStarChange() && it.getStarred())
|
||||
.map(ItemReadStarState::getRemoteId)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
syncData.setUnstarredItemsIds(itemStateChanges.stream()
|
||||
syncData.setUnstarredIds(itemStateChanges.stream()
|
||||
.filter(it -> it.getStarChange() && !it.getStarred())
|
||||
.map(ItemReadStarState::getRemoteId)
|
||||
.collect(Collectors.toList()));
|
||||
|
|
|
@ -42,7 +42,19 @@ class FreshRSSRepository(
|
|||
): Pair<SyncResult, ErrorResult> = throw NotImplementedError("This method can't be called here")
|
||||
|
||||
override suspend fun synchronize(): SyncResult {
|
||||
val syncData = FreshRSSSyncData()
|
||||
val itemStateChanges = database.newItemStateChangeDao()
|
||||
.selectItemStateChanges(account.id)
|
||||
|
||||
val syncData = FreshRSSSyncData(
|
||||
readIds = itemStateChanges.filter { it.readChange && it.read }
|
||||
.map { it.remoteId },
|
||||
unreadIds = itemStateChanges.filter { it.readChange && !it.read }
|
||||
.map { it.remoteId },
|
||||
starredIds = itemStateChanges.filter { it.starChange && it.starred }
|
||||
.map { it.remoteId },
|
||||
unstarredIds = itemStateChanges.filter { it.starChange && !it.starred }
|
||||
.map { it.remoteId }
|
||||
)
|
||||
|
||||
val syncType: SyncType
|
||||
if (account.lastModified != 0L) {
|
||||
|
@ -65,6 +77,8 @@ class FreshRSSRepository(
|
|||
|
||||
account.lastModified = newLastModified
|
||||
database.newAccountDao().updateLastModified(newLastModified, account.id)
|
||||
|
||||
database.newItemStateChangeDao().resetStateChanges(account.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,28 +5,26 @@ import com.readrops.db.Database
|
|||
import com.readrops.db.entities.Feed
|
||||
import com.readrops.db.entities.Folder
|
||||
import com.readrops.db.entities.Item
|
||||
import com.readrops.db.entities.ItemState
|
||||
import com.readrops.db.entities.account.Account
|
||||
|
||||
typealias ErrorResult = Map<Feed, Exception>
|
||||
|
||||
abstract class ARepository(
|
||||
val database: Database,
|
||||
val account: Account
|
||||
) {
|
||||
interface Repository {
|
||||
|
||||
/**
|
||||
* This method is intended for remote accounts.
|
||||
*/
|
||||
abstract suspend fun login(account: Account)
|
||||
suspend fun login(account: Account)
|
||||
|
||||
/**
|
||||
* Global synchronization for the local account.
|
||||
* @param selectedFeeds feeds to be updated
|
||||
* @param onUpdate get synchronization status
|
||||
* @return returns the result of the synchronization used by notifications
|
||||
* @param selectedFeeds feeds to be updated, will fetch all account feeds if list is empty
|
||||
* @param onUpdate notify each feed update
|
||||
* @return the result of the synchronization used by notifications
|
||||
* and errors per feed if occurred to be transmitted to the user
|
||||
*/
|
||||
abstract suspend fun synchronize(
|
||||
suspend fun synchronize(
|
||||
selectedFeeds: List<Feed>,
|
||||
onUpdate: (Feed) -> Unit
|
||||
): Pair<SyncResult, ErrorResult>
|
||||
|
@ -34,16 +32,23 @@ abstract class ARepository(
|
|||
/**
|
||||
* Global synchronization for remote accounts. Unlike the local account, remote accounts
|
||||
* won't benefit from synchronization status and granular synchronization
|
||||
* @return the result of the synchronization
|
||||
*/
|
||||
abstract suspend fun synchronize(): SyncResult
|
||||
suspend fun synchronize(): SyncResult
|
||||
|
||||
abstract suspend fun insertNewFeeds(newFeeds: List<Feed>, onUpdate: (Feed) -> Unit): ErrorResult
|
||||
/**
|
||||
* Insert new feeds by notifying each of them
|
||||
* @param newFeeds feeds to insert
|
||||
* @param onUpdate notify each feed insertion
|
||||
* @return errors by feed
|
||||
*/
|
||||
suspend fun insertNewFeeds(newFeeds: List<Feed>, onUpdate: (Feed) -> Unit): ErrorResult
|
||||
}
|
||||
|
||||
abstract class BaseRepository(
|
||||
database: Database,
|
||||
account: Account,
|
||||
) : ARepository(database, account) {
|
||||
val database: Database,
|
||||
val account: Account,
|
||||
) : Repository {
|
||||
|
||||
open suspend fun updateFeed(feed: Feed) =
|
||||
database.newFeedDao().updateFeedFields(feed.id, feed.name!!, feed.url!!, feed.folderId)
|
||||
|
@ -57,11 +62,39 @@ abstract class BaseRepository(
|
|||
open suspend fun deleteFolder(folder: Folder) = database.newFolderDao().delete(folder)
|
||||
|
||||
open suspend fun setItemReadState(item: Item) {
|
||||
database.newItemDao().updateReadState(item.id, item.isRead)
|
||||
// TODO handle Nextcloud News case
|
||||
if (account.config.useSeparateState) {
|
||||
database.newItemStateChangeDao().upsertItemReadStateChange(item, account.id, true)
|
||||
database.newItemStateDao().upsertItemReadState(
|
||||
ItemState(
|
||||
id = 0,
|
||||
read = item.isRead,
|
||||
starred = item.isStarred,
|
||||
remoteId = item.remoteId!!,
|
||||
accountId = account.id
|
||||
)
|
||||
)
|
||||
} else {
|
||||
database.newItemDao().updateReadState(item.id, item.isRead)
|
||||
}
|
||||
}
|
||||
|
||||
open suspend fun setItemStarState(item: Item) {
|
||||
database.newItemDao().updateStarState(item.id, item.isStarred)
|
||||
// TODO handle Nextcloud News case
|
||||
if (account.config.useSeparateState) {
|
||||
database.newItemStateChangeDao().upsertItemStarStateChange(item, account.id, true)
|
||||
database.newItemStateDao().upsertItemStarState(
|
||||
ItemState(
|
||||
id = 0,
|
||||
read = item.isRead,
|
||||
starred = item.isStarred,
|
||||
remoteId = item.remoteId!!,
|
||||
accountId = account.id
|
||||
)
|
||||
)
|
||||
} else {
|
||||
database.newItemDao().updateStarState(item.id, item.isStarred)
|
||||
}
|
||||
}
|
||||
|
||||
open suspend fun setAllItemsRead(accountId: Int) {
|
|
@ -17,13 +17,13 @@ interface NewItemStateChangeDao: NewBaseDao<ItemStateChange> {
|
|||
"ItemStateChange.read_change, ItemStateChange.star_change, Item.remoteId " +
|
||||
"From ItemStateChange Inner Join Item On ItemStateChange.id = Item.id " +
|
||||
"Left Join ItemState On ItemState.remote_id = Item.remoteId Where ItemStateChange.account_id = :accountId")
|
||||
suspend fun getItemStateChanges(accountId: Int): List<ItemReadStarState>
|
||||
suspend fun selectItemStateChanges(accountId: Int): List<ItemReadStarState>
|
||||
|
||||
@Query("Select Item.read, Item.starred," +
|
||||
"ItemStateChange.read_change, ItemStateChange.star_change, Item.remoteId " +
|
||||
"From ItemStateChange Inner Join Item On ItemStateChange.id = Item.id " +
|
||||
"Where ItemStateChange.account_id = :accountId")
|
||||
suspend fun getNextcloudNewsStateChanges(accountId: Int): List<ItemReadStarState>
|
||||
suspend fun selectNextcloudNewsStateChanges(accountId: Int): List<ItemReadStarState>
|
||||
|
||||
@Query("Select Case When :itemId In (Select id From ItemStateChange Where read_change = 1) Then 1 Else 0 End")
|
||||
suspend fun readStateChangeExists(itemId: Int): Boolean
|
||||
|
@ -34,9 +34,9 @@ interface NewItemStateChangeDao: NewBaseDao<ItemStateChange> {
|
|||
suspend fun upsertItemReadStateChange(item: Item, accountId: Int, useSeparateState: Boolean) {
|
||||
if (itemStateChangeExists(item.id, accountId)) {
|
||||
val oldItemReadState = if (useSeparateState)
|
||||
getItemReadState(item.remoteId!!, accountId)
|
||||
selectItemReadState(item.remoteId!!, accountId)
|
||||
else
|
||||
getStandardItemReadState(item.remoteId!!, accountId)
|
||||
selectStandardItemReadState(item.remoteId!!, accountId)
|
||||
|
||||
val readChange = item.isRead != oldItemReadState
|
||||
|
||||
|
@ -58,9 +58,9 @@ interface NewItemStateChangeDao: NewBaseDao<ItemStateChange> {
|
|||
suspend fun upsertItemStarStateChange(item: Item, accountId: Int, useSeparateState: Boolean) {
|
||||
if (itemStateChangeExists(item.id, accountId)) {
|
||||
val oldItemStarState = if (useSeparateState)
|
||||
getItemStarState(item.remoteId!!, accountId)
|
||||
selectItemStarState(item.remoteId!!, accountId)
|
||||
else
|
||||
getStandardItemStarState(item.remoteId!!, accountId)
|
||||
selectStandardItemStarState(item.remoteId!!, accountId)
|
||||
|
||||
val starChange = item.isStarred != oldItemStarState
|
||||
|
||||
|
@ -86,16 +86,16 @@ interface NewItemStateChangeDao: NewBaseDao<ItemStateChange> {
|
|||
fun itemStateChangeExists(id: Int, accountId: Int): Boolean
|
||||
|
||||
@Query("Select read From ItemState Where remote_id = :remoteId And account_id = :accountId")
|
||||
fun getItemReadState(remoteId: String, accountId: Int): Boolean
|
||||
fun selectItemReadState(remoteId: String, accountId: Int): Boolean
|
||||
|
||||
@Query("Select read From Item Inner Join Feed On Item.feed_id = Feed.id Where Item.remoteId = :remoteId And account_id = :accountId")
|
||||
fun getStandardItemReadState(remoteId: String, accountId: Int): Boolean
|
||||
fun selectStandardItemReadState(remoteId: String, accountId: Int): Boolean
|
||||
|
||||
@Query("Select starred From ItemState Where remote_id = :remoteId And account_id = :accountId")
|
||||
fun getItemStarState(remoteId: String, accountId: Int): Boolean
|
||||
fun selectItemStarState(remoteId: String, accountId: Int): Boolean
|
||||
|
||||
@Query("Select starred From Item Inner Join Feed On Item.feed_id = Feed.id Where Item.remoteId = :remoteId And account_id = :accountId")
|
||||
fun getStandardItemStarState(remoteId: String, accountId: Int): Boolean
|
||||
fun selectStandardItemStarState(remoteId: String, accountId: Int): Boolean
|
||||
|
||||
@Query("Update ItemStateChange set read_change = :readChange Where id = :id")
|
||||
fun updateItemReadStateChange(readChange: Boolean, id: Int)
|
||||
|
|
Loading…
Reference in New Issue