Commit b9c92785 authored by Christophe Henry's avatar Christophe Henry

Merge branch '65' into 'develop'

Fix #65: synchronisation fails when article JSON is malformed

Closes #65

See merge request !50
parents 0d230fc8 906abf0d
......@@ -7,6 +7,7 @@ import org.joda.time.LocalDateTime
data class ContentItemsHandler(
val id: String,
val updated: Long,
@JsonDeserialize(using = ContentItemsDeserializer::class)
val items: ContentItems,
val continuation: String = ""
)
......
......@@ -2,23 +2,56 @@ package fr.chenry.android.freshrss.store.api.models
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.datatype.joda.deser.LocalDateTimeDeserializer
import com.github.kittinunf.fuel.core.ResponseDeserializable
import fr.chenry.android.freshrss.utils.*
import org.joda.time.DateTimeZone
import org.joda.time.LocalDateTime
import org.json.JSONObject
class MicroSecTimestampDeserializer : LocalDateTimeDeserializer() {
class ContentItemsDeserializer: JsonDeserializer<ContentItems>() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): ContentItems =
p?.let {_ ->
Try {
JACKSON_OBJECT_MAPPER.readTree<ArrayNode>(p).mapNotNull {
Try {JACKSON_OBJECT_MAPPER.convertValue(it, ContentItem::class.java)}.getOrNull()
}
}.getOrDefault(listOf())
} ?: listOf()
}
class MicroSecTimestampDeserializer: LocalDateTimeDeserializer() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): LocalDateTime {
val tz = if (_format.isTimezoneExplicit) _format.timeZone else DateTimeZone.forTimeZone(ctxt?.timeZone)
val tz = if(_format.isTimezoneExplicit) _format.timeZone else DateTimeZone.forTimeZone(ctxt?.timeZone)
return LocalDateTime(p?.valueAsString?.toLongOrNull()?.div(1000) ?: 0, tz)
}
}
class MilliSecTimestampDeserializer : LocalDateTimeDeserializer() {
class MilliSecTimestampDeserializer: LocalDateTimeDeserializer() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): LocalDateTime {
val tz = if (_format.isTimezoneExplicit) _format.timeZone else DateTimeZone.forTimeZone(ctxt?.timeZone)
val tz = if(_format.isTimezoneExplicit) _format.timeZone else DateTimeZone.forTimeZone(ctxt?.timeZone)
return LocalDateTime(p?.valueAsString?.toLongOrNull() ?: 0, tz)
}
}
class PublishedDateDeserializer : LocalDateTimeDeserializer() {
class PublishedDateDeserializer: LocalDateTimeDeserializer() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): LocalDateTime =
LocalDateTime((p?.valueAsLong ?: 0L) * 1000L)
}
object TagsDeserializer: ResponseDeserializable<List<String>> {
override fun deserialize(content: String): List<String> {
return try {
val jsonArray = JSONObject(content).optJSONArray("tags")
val initialCapacity = jsonArray.length()
val tags = ArrayList<String>(initialCapacity)
for(i in 0 until initialCapacity) tags.add(jsonArray.optJSONObject(i).optString("id"))
tags
} catch(e: Exception) {
this.e(e)
listOf()
}
}
}
package fr.chenry.android.freshrss.store.api.models
import fr.chenry.android.freshrss.utils.e
import com.github.kittinunf.fuel.core.ResponseDeserializable
import org.json.JSONObject
object TagsDeserializer : ResponseDeserializable<List<String>> {
override fun deserialize(content: String): List<String> {
return try {
val jsonArray = JSONObject(content).optJSONArray("tags")
val initialCapacity = jsonArray.length()
val tags = ArrayList<String>(initialCapacity)
for (i in 0 until initialCapacity) tags.add(jsonArray.optJSONObject(i).optString("id"))
tags
} catch (e: Exception) {
this.e(e)
listOf()
}
}
}
......@@ -22,7 +22,7 @@ import org.apache.commons.text.StringEscapeUtils
import org.apache.commons.text.WordUtils
import kotlin.reflect.full.companionObjectInstance
val JACKSON_OBJECT_MAPPER = ObjectMapper().registerKotlinModule()
val JACKSON_OBJECT_MAPPER: ObjectMapper = ObjectMapper().registerKotlinModule()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(JodaModule())
......
package fr.chenry.android.freshrss
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTestActivity {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
package fr.chenry.android.freshrss.store.api.models
import fr.chenry.android.freshrss.utils.JACKSON_OBJECT_MAPPER
import org.joda.time.LocalDateTime
import org.junit.Assert.assertEquals
import org.junit.Test
class ContentItemsHandlerTest {
@Test
fun testDeserNominal() {
val json = javaClass.classLoader!!
.getResource("fixtures/ContentItemsHandlerFixtures/nominal.json")
.readText()
val items: ContentItems = listOf(
ContentItem(
"tag:google.com,2005:reader/item/00058167f526c8be",
LocalDateTime("2019-02-08T20:39:38.127"),
LocalDateTime("2019-02-08T20:39:38.127"),
LocalDateTime("2019-01-14T20:18:00.000"),
"Eolas contre Institut pour la Justice : Episode 1. Le Compteur Fantôme.",
listOf(Href("http://localhost/post/2019/01/14/abcd")),
listOf(
"user/-/state/com.google/reading-list",
"user/-/label/Misc",
"user/-/state/com.google/read"
),
ContentItemOrigin("feed/10", "Journal d'un avocat"),
Summary("\n<p><span>L’affaire judiciaire m’ayant"),
"Eolas"
),
ContentItem(
"tag:google.com,2005:reader/item/00058167f526c759",
LocalDateTime("2019-02-08T20:39:38.127"),
LocalDateTime("2019-02-08T20:39:38.127"),
LocalDateTime("2018-09-20T14:10:00.000"),
"Petite leçon de droit à l’attention de Madame Le Pen (et de M. Mélenchon qui passait par là)",
listOf(Href("http://localhost/post/2019/01/14/defg")),
listOf(
"user/-/state/com.google/reading-list",
"user/-/label/Misc",
"user/-/state/com.google/read"
),
ContentItemOrigin("feed/10", "Journal d'un avocat"),
Summary("\n<p>Mon ancienne consœur Marion “Marine” Le Pen s’offusque à qui veut l’entendre,"),
"Eolas"
)
)
val target = JACKSON_OBJECT_MAPPER.readValue(json, ContentItemsHandler::class.java)
val expected =
ContentItemsHandler("user/-/state/com.google/reading-list", 1563874362, items, "1549658378126328")
assertEquals(expected, target)
}
@Test
fun testDeserNullContent() {
val json = javaClass.classLoader!!
.getResource("fixtures/ContentItemsHandlerFixtures/with-null-content.json")
.readText()
val items: ContentItems = listOf(
ContentItem(
"tag:google.com,2005:reader/item/00058167f526c8be",
LocalDateTime("2019-02-08T20:39:38.127"),
LocalDateTime("2019-02-08T20:39:38.127"),
LocalDateTime("2019-01-14T20:18:00.000"),
"Eolas contre Institut pour la Justice : Episode 1. Le Compteur Fantôme.",
listOf(Href("http://localhost/post/2019/01/14/abcd")),
listOf(
"user/-/state/com.google/reading-list",
"user/-/label/Misc",
"user/-/state/com.google/read"
),
ContentItemOrigin("feed/10", "Journal d'un avocat"),
Summary("\n<p><span>L’affaire judiciaire m’ayant"),
"Eolas"
)
)
val target = JACKSON_OBJECT_MAPPER.readValue(json, ContentItemsHandler::class.java)
val expected =
ContentItemsHandler("user/-/state/com.google/reading-list", 1563874362, items, "1549658378126328")
assertEquals(expected, target)
}
}
{
"id": "user/-/state/com.google/reading-list",
"updated": 1563874362,
"items": [
{
"id": "tag:google.com,2005:reader/item/00058167f526c8be",
"crawlTimeMsec": "1549658378127",
"timestampUsec": "1549658378127550",
"published": 1547493480,
"title": "Eolas contre Institut pour la Justice : Episode 1. Le Compteur Fantôme.",
"summary": {
"content": "\n<p><span>L’affaire judiciaire m’ayant"
},
"alternate": [
{
"href": "http://localhost/post/2019/01/14/abcd"
}
],
"categories": [
"user/-/state/com.google/reading-list",
"user/-/label/Misc",
"user/-/state/com.google/read"
],
"origin": {
"streamId": "feed/10",
"title": "Journal d'un avocat"
},
"author": "Eolas"
},
{
"id": "tag:google.com,2005:reader/item/00058167f526c759",
"crawlTimeMsec": "1549658378127",
"timestampUsec": "1549658378127193",
"published": 1537445400,
"title": "Petite leçon de droit à l’attention de Madame Le Pen (et de M. Mélenchon qui passait par là)",
"summary": {
"content": "\n<p>Mon ancienne consœur Marion “Marine” Le Pen s’offusque à qui veut l’entendre,"
},
"alternate": [
{
"href": "http://localhost/post/2019/01/14/defg"
}
],
"categories": [
"user/-/state/com.google/reading-list",
"user/-/label/Misc",
"user/-/state/com.google/read"
],
"origin": {
"streamId": "feed/10",
"title": "Journal d'un avocat"
},
"author": "Eolas"
}
],
"continuation": "1549658378126328"
}
{
"id": "user/-/state/com.google/reading-list",
"updated": 1563874362,
"items": [
{
"id": "tag:google.com,2005:reader/item/00058167f526c8be",
"crawlTimeMsec": "1549658378127",
"timestampUsec": "1549658378127550",
"published": 1547493480,
"title": "Eolas contre Institut pour la Justice : Episode 1. Le Compteur Fantôme.",
"summary": {
"content": "\n<p><span>L’affaire judiciaire m’ayant"
},
"alternate": [
{
"href": "http://localhost/post/2019/01/14/abcd"
}
],
"categories": [
"user/-/state/com.google/reading-list",
"user/-/label/Misc",
"user/-/state/com.google/read"
],
"origin": {
"streamId": "feed/10",
"title": "Journal d'un avocat"
},
"author": "Eolas"
},
{
"id": "tag:google.com,2005:reader/item/00058167f526c759",
"crawlTimeMsec": "1549658378127",
"timestampUsec": "1549658378127193",
"published": 1537445400,
"title": "",
"alternate": [
{
"href": "http://www.maitre-eolas.fr/post/2018/09/20/Petite-le%C3%A7on-de-droit-%C3%A0-l%E2%80%99attention-de-Madame-Le-Pen"
}
],
"categories": [
"user/-/state/com.google/reading-list",
"user/-/label/Misc",
"user/-/state/com.google/read"
],
"origin": {
"streamId": "feed/10",
"title": "Journal d'un avocat"
},
"author": "Eolas"
}
],
"continuation": "1549658378126328"
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment