mirror of
https://github.com/readrops/Readrops.git
synced 2025-02-01 11:16:49 +01:00
Merge branch 'refs/heads/master' into develop
# Conflicts: # api/src/test/java/com/readrops/api/services/nextcloudnews/NextcloudNewsDataSourceTest.kt # db/src/main/java/com/readrops/db/dao/ItemDao.kt
This commit is contained in:
commit
1a727f1f1b
@ -1,3 +1,8 @@
|
||||
# v2.0.3
|
||||
- Fix Fever API compatibility with TinyTiny RSS and yarr, should also fix other providers (#228 + #229)
|
||||
- Fix Nextcloud News item duplicates when syncing which would made the app unusable
|
||||
- Fix Nextcloud News item parsing: items with no title will be ignored
|
||||
|
||||
# v2.0.2
|
||||
- Fix crash when opening app from a notification (#223)
|
||||
- Fix Fever API synchronization error (#228)
|
||||
|
@ -1,12 +1,10 @@
|
||||
package com.readrops.api.services.fever.adapters
|
||||
|
||||
import com.readrops.api.utils.exceptions.ParseException
|
||||
import com.readrops.api.utils.extensions.skipField
|
||||
import com.readrops.api.utils.extensions.toBoolean
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonReader.Token
|
||||
import com.squareup.moshi.JsonWriter
|
||||
import com.squareup.moshi.ToJson
|
||||
|
||||
@ -21,21 +19,17 @@ class FeverAPIAdapter : JsonAdapter<Boolean>() {
|
||||
override fun fromJson(reader: JsonReader): Boolean = with(reader) {
|
||||
return try {
|
||||
beginObject()
|
||||
skipField()
|
||||
|
||||
var authenticated = 0
|
||||
if (nextName() == "auth") {
|
||||
authenticated = nextInt()
|
||||
} else {
|
||||
skipValue()
|
||||
}
|
||||
|
||||
while (peek() == Token.NAME) {
|
||||
skipField()
|
||||
var authenticated = false
|
||||
while (hasNext()) {
|
||||
when (nextName()) {
|
||||
"auth" -> authenticated = nextInt().toBoolean()
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
|
||||
endObject()
|
||||
authenticated.toBoolean()
|
||||
authenticated
|
||||
} catch (e: Exception) {
|
||||
throw ParseException(e.message)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package com.readrops.api.services.fever.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.readrops.api.utils.exceptions.ParseException
|
||||
import com.readrops.api.utils.extensions.skipField
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.ToJson
|
||||
@ -19,7 +18,6 @@ class FeverFaviconsAdapter {
|
||||
@ToJson
|
||||
fun toJson(favicons: List<Favicon>) = ""
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
@SuppressLint("CheckResult")
|
||||
@FromJson
|
||||
fun fromJson(reader: JsonReader): List<Favicon> = with(reader) {
|
||||
@ -27,47 +25,54 @@ class FeverFaviconsAdapter {
|
||||
val favicons = arrayListOf<Favicon>()
|
||||
|
||||
beginObject()
|
||||
|
||||
repeat(3) {
|
||||
skipField()
|
||||
}
|
||||
|
||||
nextName() // beginning of favicon array
|
||||
beginArray()
|
||||
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
when (nextName()) {
|
||||
"favicons" -> {
|
||||
beginArray()
|
||||
|
||||
var id = 0
|
||||
var data: ByteArray? = null
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
parseFavicon(reader)?.let { favicons += it }
|
||||
|
||||
while (hasNext()) {
|
||||
when (selectName(NAMES)) {
|
||||
0 -> id = nextInt()
|
||||
1 -> data = Base64.decode(nextString().substringAfter("base64,"))
|
||||
else -> skipValue()
|
||||
endObject()
|
||||
}
|
||||
|
||||
endArray()
|
||||
}
|
||||
else -> skipValue()
|
||||
}
|
||||
|
||||
if (id > 0 && data != null) {
|
||||
favicons += Favicon(
|
||||
id = id,
|
||||
data = data,
|
||||
)
|
||||
}
|
||||
|
||||
endObject()
|
||||
}
|
||||
|
||||
endArray()
|
||||
endObject()
|
||||
|
||||
favicons
|
||||
} catch (e: Exception) {
|
||||
throw ParseException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
private fun parseFavicon(reader: JsonReader): Favicon? = with(reader) {
|
||||
var id = 0
|
||||
var data: ByteArray? = null
|
||||
|
||||
while (hasNext()) {
|
||||
when (selectName(NAMES)) {
|
||||
0 -> id = nextInt()
|
||||
1 -> data = Base64.decode(nextString().substringAfter("base64,"))
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
|
||||
if (id > 0 && data != null) {
|
||||
return Favicon(
|
||||
id = id,
|
||||
data = data,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NAMES: JsonReader.Options = JsonReader.Options.of("id", "data")
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||
import com.readrops.api.utils.exceptions.ParseException
|
||||
import com.readrops.api.utils.extensions.nextNonEmptyString
|
||||
import com.readrops.api.utils.extensions.nextNullableString
|
||||
import com.readrops.api.utils.extensions.skipField
|
||||
import com.readrops.db.entities.Feed
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
@ -30,63 +29,34 @@ class FeverFeedsAdapter : JsonAdapter<FeverFeeds>() {
|
||||
val feedsGroups = mutableMapOf<Int, List<Int>>()
|
||||
|
||||
beginObject()
|
||||
|
||||
while (nextName() != "feeds") {
|
||||
skipValue()
|
||||
}
|
||||
|
||||
// feeds array
|
||||
beginArray()
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
when (nextName()) {
|
||||
"feeds" -> {
|
||||
beginArray()
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
feeds += parseFeed(reader, favicons)
|
||||
|
||||
val feed = Feed()
|
||||
while (hasNext()) {
|
||||
with(feed) {
|
||||
when (selectName(NAMES)) {
|
||||
0 -> remoteId = nextInt().toString()
|
||||
1 -> favicons[nextInt()] = remoteId!!
|
||||
2 -> name = nextNonEmptyString()
|
||||
3 -> url = nextNonEmptyString()
|
||||
4 -> siteUrl = nextNullableString()
|
||||
else -> skipValue()
|
||||
endObject()
|
||||
}
|
||||
|
||||
endArray()
|
||||
}
|
||||
}
|
||||
"feeds_groups" -> {
|
||||
beginArray()
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
|
||||
feeds += feed
|
||||
endObject()
|
||||
}
|
||||
val (folderId, feedsIds) = parseFeedsGroups(reader)
|
||||
folderId?.let { feedsGroups[it] = feedsIds }
|
||||
|
||||
endArray()
|
||||
endObject()
|
||||
}
|
||||
|
||||
while (nextName() != "feeds_groups") {
|
||||
skipValue()
|
||||
}
|
||||
|
||||
// feeds_groups array
|
||||
beginArray()
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
|
||||
var folderId: Int? = null
|
||||
val feedsIds = mutableListOf<Int>()
|
||||
while (hasNext()) {
|
||||
when (selectName(JsonReader.Options.of("group_id", "feed_ids"))) {
|
||||
0 -> folderId = nextInt()
|
||||
1 -> feedsIds += nextNonEmptyString().split(",").map { it.toInt() }
|
||||
else -> skipValue()
|
||||
endArray()
|
||||
}
|
||||
else -> skipValue()
|
||||
}
|
||||
|
||||
folderId?.let { feedsGroups[it] = feedsIds }
|
||||
endObject()
|
||||
}
|
||||
|
||||
endArray()
|
||||
|
||||
while (peek() != JsonReader.Token.END_OBJECT) {
|
||||
skipField()
|
||||
}
|
||||
|
||||
endObject()
|
||||
@ -101,6 +71,39 @@ class FeverFeedsAdapter : JsonAdapter<FeverFeeds>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseFeed(reader: JsonReader, favicons: MutableMap<Int, String>): Feed = with(reader) {
|
||||
val feed = Feed()
|
||||
while (hasNext()) {
|
||||
with(feed) {
|
||||
when (selectName(NAMES)) {
|
||||
0 -> remoteId = nextInt().toString()
|
||||
1 -> favicons[nextInt()] = remoteId!!
|
||||
2 -> name = nextNonEmptyString()
|
||||
3 -> url = nextNonEmptyString()
|
||||
4 -> siteUrl = nextNullableString()
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return feed
|
||||
}
|
||||
|
||||
private fun parseFeedsGroups(reader: JsonReader): Pair<Int?, List<Int>> = with(reader) {
|
||||
var folderId: Int? = null
|
||||
val feedsIds = mutableListOf<Int>()
|
||||
|
||||
while (hasNext()) {
|
||||
when (selectName(JsonReader.Options.of("group_id", "feed_ids"))) {
|
||||
0 -> folderId = nextInt()
|
||||
1 -> feedsIds += nextNonEmptyString().split(",").map { it.toInt() }
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
|
||||
folderId to feedsIds
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NAMES: JsonReader.Options =
|
||||
JsonReader.Options.of("id", "favicon_id", "title", "url", "site_url")
|
||||
|
@ -3,8 +3,6 @@ package com.readrops.api.services.fever.adapters
|
||||
import android.annotation.SuppressLint
|
||||
import com.readrops.api.utils.exceptions.ParseException
|
||||
import com.readrops.api.utils.extensions.nextNonEmptyString
|
||||
import com.readrops.api.utils.extensions.skipField
|
||||
import com.readrops.api.utils.extensions.skipToEnd
|
||||
import com.readrops.db.entities.Folder
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonReader
|
||||
@ -22,35 +20,35 @@ class FeverFoldersAdapter {
|
||||
val folders = arrayListOf<Folder>()
|
||||
|
||||
beginObject()
|
||||
|
||||
repeat(3) {
|
||||
skipField()
|
||||
}
|
||||
|
||||
nextName() // beginning of folders array
|
||||
beginArray()
|
||||
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
when (nextName()) {
|
||||
"groups" -> {
|
||||
beginArray()
|
||||
|
||||
val folder = Folder()
|
||||
while (hasNext()) {
|
||||
with(folder) {
|
||||
when (selectName(NAMES)) {
|
||||
0 -> remoteId = nextInt().toString()
|
||||
1 -> name = nextNonEmptyString()
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
|
||||
val folder = Folder()
|
||||
while (hasNext()) {
|
||||
with(folder) {
|
||||
when (selectName(NAMES)) {
|
||||
0 -> remoteId = nextInt().toString()
|
||||
1 -> name = nextNonEmptyString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
folders += folder
|
||||
endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
folders += folder
|
||||
endObject()
|
||||
endArray()
|
||||
}
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
|
||||
endArray()
|
||||
skipToEnd()
|
||||
endObject()
|
||||
|
||||
folders
|
||||
} catch (e: Exception) {
|
||||
throw ParseException(e.message)
|
||||
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||
import com.readrops.api.utils.exceptions.ParseException
|
||||
import com.readrops.api.utils.extensions.nextNonEmptyString
|
||||
import com.readrops.api.utils.extensions.nextNullableString
|
||||
import com.readrops.api.utils.extensions.skipField
|
||||
import com.readrops.api.utils.extensions.toBoolean
|
||||
import com.readrops.db.entities.Item
|
||||
import com.readrops.db.util.DateUtils
|
||||
@ -24,56 +23,59 @@ class FeverItemsAdapter {
|
||||
val items = arrayListOf<Item>()
|
||||
|
||||
beginObject()
|
||||
while (nextName() != "items") {
|
||||
skipValue()
|
||||
}
|
||||
|
||||
beginArray()
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
when (nextName()) {
|
||||
"items" -> {
|
||||
beginArray()
|
||||
while (hasNext()) {
|
||||
beginObject()
|
||||
items += parseItem(reader)
|
||||
|
||||
val item = Item()
|
||||
while (hasNext()) {
|
||||
with(item) {
|
||||
when (selectName(NAMES)) {
|
||||
0 -> {
|
||||
remoteId = if (reader.peek() == JsonReader.Token.STRING) {
|
||||
nextNonEmptyString()
|
||||
} else {
|
||||
nextInt().toString()
|
||||
}
|
||||
}
|
||||
1 -> feedRemoteId = nextNonEmptyString()
|
||||
2 -> title = nextNonEmptyString()
|
||||
3 -> author = nextNullableString()
|
||||
4 -> content = nextNullableString()
|
||||
5 -> link = nextNullableString()
|
||||
6 -> isRead = nextInt().toBoolean()
|
||||
7 -> isStarred = nextInt().toBoolean()
|
||||
8 -> pubDate = DateUtils.fromEpochSeconds(nextLong())
|
||||
else -> skipValue()
|
||||
endObject()
|
||||
}
|
||||
|
||||
endArray()
|
||||
}
|
||||
else -> skipValue()
|
||||
}
|
||||
|
||||
items += item
|
||||
endObject()
|
||||
}
|
||||
|
||||
endArray()
|
||||
|
||||
while (peek() != JsonReader.Token.END_OBJECT) {
|
||||
skipField()
|
||||
}
|
||||
|
||||
endObject()
|
||||
|
||||
items
|
||||
} catch (e: Exception) {
|
||||
throw ParseException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseItem(reader: JsonReader): Item = with(reader) {
|
||||
val item = Item()
|
||||
|
||||
while (hasNext()) {
|
||||
with(item) {
|
||||
when (selectName(NAMES)) {
|
||||
0 -> {
|
||||
remoteId = if (reader.peek() == JsonReader.Token.STRING) {
|
||||
nextNonEmptyString()
|
||||
} else {
|
||||
nextInt().toString()
|
||||
}
|
||||
}
|
||||
1 -> feedRemoteId = nextNonEmptyString()
|
||||
2 -> title = nextNonEmptyString()
|
||||
3 -> author = nextNullableString()
|
||||
4 -> content = nextNullableString()
|
||||
5 -> link = nextNullableString()
|
||||
6 -> isRead = nextInt().toBoolean()
|
||||
7 -> isStarred = nextInt().toBoolean()
|
||||
8 -> pubDate = DateUtils.fromEpochSeconds(nextLong())
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NAMES: JsonReader.Options = JsonReader.Options.of(
|
||||
"id", "feed_id", "title", "author", "html", "url",
|
||||
|
@ -2,7 +2,6 @@ package com.readrops.api.services.fever.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.readrops.api.utils.exceptions.ParseException
|
||||
import com.readrops.api.utils.extensions.skipField
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.ToJson
|
||||
@ -17,12 +16,14 @@ class FeverItemsIdsAdapter {
|
||||
fun fromJson(reader: JsonReader): List<String> = with(reader) {
|
||||
return try {
|
||||
beginObject()
|
||||
repeat(3) {
|
||||
skipField()
|
||||
}
|
||||
|
||||
nextName() // (unread|saved)_item_ids field
|
||||
val ids = nextString().split(",")
|
||||
val ids = arrayListOf<String>()
|
||||
while (hasNext()) {
|
||||
when (nextName()) {
|
||||
"unread_item_ids" -> ids.addAll(nextString().split(","))
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
|
||||
endObject()
|
||||
ids
|
||||
|
@ -4,8 +4,8 @@ import com.readrops.api.TestUtils
|
||||
import com.squareup.moshi.Moshi
|
||||
import okio.Buffer
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class FeverAPIAdapterTest {
|
||||
|
||||
@ -18,15 +18,13 @@ class FeverAPIAdapterTest {
|
||||
fun authenticatedTest() {
|
||||
val stream = TestUtils.loadResource("services/fever/successful_auth.json")
|
||||
|
||||
val value = adapter.fromJson(Buffer().readFrom(stream))!!
|
||||
assertEquals(value, true)
|
||||
assertTrue { adapter.fromJson(Buffer().readFrom(stream))!! }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unauthenticatedTest() {
|
||||
val stream = TestUtils.loadResource("services/fever/unsuccessful_auth.json")
|
||||
|
||||
val value = adapter.fromJson(Buffer().readFrom(stream))!!
|
||||
assertFalse { value }
|
||||
assertFalse { adapter.fromJson(Buffer().readFrom(stream))!! }
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ class FeverFaviconsAdapterTest {
|
||||
|
||||
assertEquals(favicons.size, 3)
|
||||
|
||||
with(favicons[0]) {
|
||||
with(favicons.first()) {
|
||||
assertEquals(id, 85)
|
||||
assertNotNull(data)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class FeverFeedsAdapterTest {
|
||||
|
||||
assertEquals(feverFeeds.feeds.size, 1)
|
||||
|
||||
with(feverFeeds.feeds[0]) {
|
||||
with(feverFeeds.feeds.first()) {
|
||||
assertEquals(name, "xda-developers")
|
||||
assertEquals(url, "https://www.xda-developers.com/feed/")
|
||||
assertEquals(siteUrl, "https://www.xda-developers.com/")
|
||||
|
@ -21,7 +21,7 @@ class FeverFoldersAdapterTest {
|
||||
|
||||
val folders = adapter.fromJson(Buffer().readFrom(stream))!!
|
||||
|
||||
with(folders[0]) {
|
||||
with(folders.first()) {
|
||||
assertEquals(name, "Libre")
|
||||
assertEquals(remoteId, "4")
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class FeverItemsAdapterTest {
|
||||
|
||||
val items = adapter.fromJson(Buffer().readFrom(stream))!!
|
||||
|
||||
with(items[0]) {
|
||||
with(items.first()) {
|
||||
assertEquals(title, "FreshRSS 1.9.0")
|
||||
assertEquals(author, "Alkarex")
|
||||
assertEquals(link, "https://github.com/FreshRSS/FreshRSS/releases/tag/1.9.0")
|
||||
|
@ -12,8 +12,8 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "com.readrops.app"
|
||||
|
||||
versionCode = 19
|
||||
versionName = "2.0.2"
|
||||
versionCode = 20
|
||||
versionName = "2.0.3"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -162,7 +162,7 @@ class NextcloudNewsRepository(
|
||||
itemsFeedsIds[item.feedRemoteId] = feedId
|
||||
}
|
||||
|
||||
if (!initialSync && feedId > 0 && database.itemDao().itemExists(item.remoteId!!, feedId)) {
|
||||
if (!initialSync && feedId > 0 && database.itemDao().itemExists(item.remoteId!!, account.id)) {
|
||||
database.itemDao()
|
||||
.updateReadAndStarState(item.remoteId!!, item.isRead, item.isStarred)
|
||||
continue
|
||||
|
@ -67,6 +67,7 @@ interface ItemDao : BaseDao<Item> {
|
||||
fun selectFeedUnreadItemsCount(query: SupportSQLiteQuery):
|
||||
Flow<Map<@MapColumn(columnName = "feed_id") Int, @MapColumn(columnName = "item_count") Int>>
|
||||
|
||||
@Query("Select case When :remoteId In (Select Item.remote_id From Item Inner Join Feed on Item.feed_id = Feed.id and account_id = :accountId) Then 1 else 0 end")
|
||||
@Query("""Select case When Exists(Select 1 From Item Inner Join Feed on Item.feed_id = Feed.id
|
||||
Where Item.remote_id = :remoteId And account_id = :accountId) Then 1 else 0 end""")
|
||||
suspend fun itemExists(remoteId: String, accountId: Int): Boolean
|
||||
}
|
3
fastlane/metadata/android/en-US/changelogs/20.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/20.txt
Normal file
@ -0,0 +1,3 @@
|
||||
* Fix Fever API compatibility with TinyTiny RSS and yarr, should also fix other providers (#228 + #229)
|
||||
* Fix Nextcloud News item duplicates when syncing which would made the app unusable
|
||||
* Fix Nextcloud News item parsing: items with no title will be ignored
|
Loading…
x
Reference in New Issue
Block a user