Sync FreshRSS items star state

This commit is contained in:
Shinokuni 2020-11-02 22:35:57 +01:00
parent c449a94260
commit cfa764e78b
9 changed files with 173 additions and 11 deletions

View File

@ -6,6 +6,7 @@ import com.readrops.api.services.freshrss.FreshRSSService
import com.readrops.api.services.freshrss.adapters.FreshRSSFeedsAdapter
import com.readrops.api.services.freshrss.adapters.FreshRSSFoldersAdapter
import com.readrops.api.services.freshrss.adapters.FreshRSSItemsAdapter
import com.readrops.api.services.freshrss.adapters.FreshRSSItemsIdsAdapter
import com.readrops.api.services.nextcloudnews.NextNewsDataSource
import com.readrops.api.services.nextcloudnews.NextNewsService
import com.readrops.api.services.nextcloudnews.adapters.NextNewsFeedsAdapter
@ -60,6 +61,7 @@ val apiModule = module {
single(named("freshrssMoshi")) {
Moshi.Builder()
.add(Types.newParameterizedType(List::class.java, Item::class.java), FreshRSSItemsAdapter())
.add(Types.newParameterizedType(List::class.java, String::class.java), FreshRSSItemsIdsAdapter())
.add(FreshRSSFeedsAdapter())
.add(FreshRSSFoldersAdapter())
.build()

View File

@ -8,9 +8,13 @@ class SyncResult {
var items: List<Item> = mutableListOf()
var starredItems: List<Item> = mutableListOf()
var feeds: List<Feed> = listOf()
var folders: List<Folder> = listOf()
var starredIds: List<String>? = null
var isError: Boolean = false
}

View File

@ -11,6 +11,8 @@ import com.readrops.db.entities.Folder;
import com.readrops.db.entities.Item;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
@ -22,9 +24,11 @@ import okhttp3.RequestBody;
public class FreshRSSDataSource {
private static final int MAX_ITEMS = 5000;
private static final int MAX_STARRED_ITEMS = 999;
public static final String GOOGLE_READ = "user/-/state/com.google/read";
public static final String GOOGLE_STARRED = "user/-/state/com.google/starred";
public static final String GOOGLE_READING_LIST = "user/-/state/com.google/reading-list";
private static final String FEED_PREFIX = "feed/";
@ -99,14 +103,22 @@ public class FreshRSSDataSource {
syncResult.setFeeds(freshRSSFeeds);
if (syncType == SyncType.INITIAL_SYNC) {
return getItems(GOOGLE_READ, MAX_ITEMS, null);
return getItems(Arrays.asList(GOOGLE_READ, GOOGLE_STARRED), MAX_ITEMS, null);
} else {
return getItems(null, MAX_ITEMS, syncData.getLastModified());
return getItems(Collections.singletonList(GOOGLE_STARRED), MAX_ITEMS, syncData.getLastModified());
}
})
.flatMap(freshRSSItems -> {
syncResult.setItems(freshRSSItems);
return getStarredItems(MAX_STARRED_ITEMS);
}).flatMap(starredItems -> {
syncResult.setStarredItems(starredItems);
return getItemsIds(null, GOOGLE_STARRED, MAX_STARRED_ITEMS);
}).flatMap(starredIds -> {
syncResult.setStarredIds(starredIds);
return Single.just(syncResult);
}));
}
@ -132,13 +144,27 @@ public class FreshRSSDataSource {
/**
* Fetch the items
*
* @param excludeTarget type of items to exclude (currently only read items)
* @param max max number of items to fetch
* @param lastModified fetch only items created after this timestamp
* @param excludeTargets type of items to exclude (read items and starred items)
* @param max max number of items to fetch
* @param lastModified fetch only items created after this timestamp
* @return the items
*/
public Single<List<Item>> getItems(@Nullable String excludeTarget, int max, @Nullable Long lastModified) {
return api.getItems(excludeTarget, max, lastModified);
public Single<List<Item>> getItems(@Nullable List<String> excludeTargets, int max, @Nullable Long lastModified) {
return api.getItems(excludeTargets, max, lastModified);
}
/**
* Fetch starred items
*
* @param max max number of items to fetch
* @return items
*/
public Single<List<Item>> getStarredItems(int max) {
return api.getStarredItems(max);
}
public Single<List<String>> getItemsIds(String excludeTarget, String includeTarget, int max) {
return api.getItemsIds(excludeTarget, includeTarget, max);
}

View File

@ -1,9 +1,9 @@
package com.readrops.api.services.freshrss;
import com.readrops.api.services.freshrss.json.FreshRSSUserInfo;
import com.readrops.db.entities.Feed;
import com.readrops.db.entities.Folder;
import com.readrops.db.entities.Item;
import com.readrops.api.services.freshrss.json.FreshRSSUserInfo;
import java.util.List;
@ -35,7 +35,13 @@ public interface FreshRSSService {
Single<List<Feed>> getFeeds();
@GET("reader/api/0/stream/contents/user/-/state/com.google/reading-list")
Single<List<Item>> getItems(@Query("xt") String excludeTarget, @Query("n") int max, @Query("ot") Long lastModified);
Single<List<Item>> getItems(@Query("xt") List<String> excludeTarget, @Query("n") int max, @Query("ot") Long lastModified);
@GET("reader/api/0/stream/contents/user/-/state/com.google/starred")
Single<List<Item>> getStarredItems(@Query("n") int max);
@GET("reader/api/0/stream/items/ids")
Single<List<String>> getItemsIds(@Query("xt") String excludeTarget, @Query("s") String includeTarget, @Query("n") int max);
@GET("reader/api/0/tag/list?output=json")
Single<List<Folder>> getFolders();

View File

@ -0,0 +1,51 @@
package com.readrops.api.services.freshrss.adapters
import android.annotation.SuppressLint
import com.readrops.api.utils.exceptions.ParseException
import com.readrops.api.utils.extensions.nextNonEmptyString
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
class FreshRSSItemsIdsAdapter : JsonAdapter<List<String>>() {
override fun toJson(writer: JsonWriter, value: List<String>?) {
// not useful here
}
@SuppressLint("CheckResult")
override fun fromJson(reader: JsonReader): List<String>? = with(reader) {
val ids = arrayListOf<String>()
return try {
beginObject()
nextName()
beginArray()
while (hasNext()) {
beginObject()
when (nextName()) {
"id" -> {
val value = nextNonEmptyString()
ids += "tag:google.com,2005:reader/item/${
value.toLong()
.toString(16).padStart(value.length, '0')
}"
}
else -> skipValue()
}
endObject()
}
endArray()
endObject()
ids
} catch (e: Exception) {
throw ParseException(e.message)
}
}
}

View File

@ -0,0 +1,30 @@
package com.readrops.api.services.freshrss.adapters
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import junit.framework.TestCase.assertEquals
import okio.Buffer
import org.junit.Test
class FreshRSSItemsIdsAdapterTest {
private val adapter = Moshi.Builder()
.add(Types.newParameterizedType(List::class.java, String::class.java), FreshRSSItemsIdsAdapter())
.build()
.adapter<List<String>>(Types.newParameterizedType(List::class.java, String::class.java))
@Test
fun validIdsTest() {
val stream = javaClass.classLoader!!.getResourceAsStream("services/freshrss/adapters/items_starred_ids.json")
val ids = adapter.fromJson(Buffer().readFrom(stream))!!
assertEquals(ids, listOf(
"tag:google.com,2005:reader/item/0005b2c17277b383",
"tag:google.com,2005:reader/item/0005b2c12d328ae4",
"tag:google.com,2005:reader/item/0005b2c0781d0737",
"tag:google.com,2005:reader/item/0005b2bf3852c293",
"tag:google.com,2005:reader/item/0005b2bebeed9f7f"
))
}
}

View File

@ -0,0 +1,19 @@
{
"itemRefs": [
{
"id": "1603918802432899"
},
{
"id": "1603917640272612"
},
{
"id": "1603914602186551"
},
{
"id": "1603909236998803"
},
{
"id": "1603907200327551"
}
]
}

View File

@ -107,6 +107,10 @@ public class FreshRSSRepository extends ARepository {
insertItems(syncResult.getItems(), syncType == SyncType.INITIAL_SYNC);
logger.addSplit("items insertion");
insertItems(syncResult.getStarredItems(), syncType == SyncType.INITIAL_SYNC);
updateItemsStarState(syncResult.getStarredIds());
account.setLastModified(newLastModified);
database.accountDao().updateLastModified(account.getId(), newLastModified);
@ -216,6 +220,8 @@ public class FreshRSSRepository extends ARepository {
}
private void insertItems(List<Item> items, boolean initialSync) {
List<Item> itemsToInsert = new ArrayList<>();
for (Item item : items) {
int feedId = database.feedDao().getFeedIdByRemoteId(item.getFeedRemoteId(), account.getId());
@ -226,9 +232,21 @@ public class FreshRSSRepository extends ARepository {
item.setFeedId(feedId);
item.setReadTime(Utils.readTimeFromString(item.getContent()));
itemsToInsert.add(item);
}
Collections.sort(items, Item::compareTo);
database.itemDao().insert(items);
if (!itemsToInsert.isEmpty()) {
Collections.sort(itemsToInsert, Item::compareTo);
database.itemDao().insert(itemsToInsert);
}
}
private void updateItemsStarState(List<String> itemsIds) {
if (itemsIds != null && !itemsIds.isEmpty()) {
database.itemDao().unstarItems(itemsIds, account.getId());
database.itemDao().starItems(itemsIds, account.getId());
}
}
}

View File

@ -94,4 +94,10 @@ public interface ItemDao extends BaseDao<Item> {
@Query("Update Item set starred = :starred, starred_changed = :starredChanged Where id = :itemId")
Completable setStarState(int itemId, boolean starred, boolean starredChanged);
@Query("Update Item set starred = 1 Where remoteId In (:ids) And feed_id In (Select id From Feed Where account_id = :accountId)")
void starItems(List<String> ids, int accountId);
@Query("Update Item set starred = 0 Where remoteId Not In (:ids) And feed_id In (Select id From Feed Where account_id = :accountId)")
void unstarItems(List<String> ids, int accountId);
}