From ca29c4acb06feb91caa7c4b9abbbdfedad3cbf57 Mon Sep 17 00:00:00 2001 From: Shinokuni Date: Tue, 15 Sep 2020 23:30:56 +0200 Subject: [PATCH] Add adapter for jsonfeed items --- .../assets/localfeed/json/json_feed.json | 6 +- .../json/json_items_other_cases.json | 28 +++++ .../json/json_items_required_elements.json | 22 ++++ .../localfeed/json/JSONItemsAdapterTest.kt | 77 ++++++++++++ .../api/localfeed/json/JSONItemsAdapter.kt | 116 ++++++++++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 api/src/androidTest/assets/localfeed/json/json_items_other_cases.json create mode 100644 api/src/androidTest/assets/localfeed/json/json_items_required_elements.json create mode 100644 api/src/androidTest/java/com/readrops/api/localfeed/json/JSONItemsAdapterTest.kt create mode 100644 api/src/main/java/com/readrops/api/localfeed/json/JSONItemsAdapter.kt diff --git a/api/src/androidTest/assets/localfeed/json/json_feed.json b/api/src/androidTest/assets/localfeed/json/json_feed.json index 35ca0e3b..4e5b032f 100644 --- a/api/src/androidTest/assets/localfeed/json/json_feed.json +++ b/api/src/androidTest/assets/localfeed/json/json_feed.json @@ -13,7 +13,11 @@ "title": "Acorn and 10.13", "content_html": "

Happy Mac OS High Sierra release day everyone.

\n

I'm happy to say that there are no known issues with Acorn 6.0.3 or Acorn 5.6.6 when running on Mac OS 10.13 High Sierra. In fact, you might even notice that some things are actually faster and it can now open HEIF images. How awesome is that?

\n

I'm also working on some 10.13 goodies for Acorn 6 folks later this year. I can't wait to share that with you, but you'll have to wait just a little bit.

\n", "date_published": "2017-09-25T14:27:27-07:00", - "url": "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html" + "url": "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html", + "author": { + "url": "this is an url", + "name": "Author 1" + } }, { "id": "http://flyingmeat.com/blog/archives/2018/2/acorn_6.1_is_out.html", diff --git a/api/src/androidTest/assets/localfeed/json/json_items_other_cases.json b/api/src/androidTest/assets/localfeed/json/json_items_other_cases.json new file mode 100644 index 00000000..e91ee941 --- /dev/null +++ b/api/src/androidTest/assets/localfeed/json/json_items_other_cases.json @@ -0,0 +1,28 @@ +{ + "items": [ + { + "id": "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html", + "title": "Acorn and 10.13", + "summary": "This is a summary", + "content_html": "content_html", + "content_text": "content_text", + "date_published": "2017-09-25T14:27:27-07:00", + "url": "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html", + "image": "https://image.com", + "authors": [ + { + "url": "url 1", + "name": "Author 1" + }, + { + "url": "url 2", + "name": "Author 2" + }, + { + "url": "url 3", + "name": "Author 3" + } + ] + } + ] +} \ No newline at end of file diff --git a/api/src/androidTest/assets/localfeed/json/json_items_required_elements.json b/api/src/androidTest/assets/localfeed/json/json_items_required_elements.json new file mode 100644 index 00000000..f46aa9a1 --- /dev/null +++ b/api/src/androidTest/assets/localfeed/json/json_items_required_elements.json @@ -0,0 +1,22 @@ +{ + "items": [ + { + "id": "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html", + "content_html": "

Happy Mac OS High Sierra release day everyone.

\n

I'm happy to say that there are no known issues with Acorn 6.0.3 or Acorn 5.6.6 when running on Mac OS 10.13 High Sierra. In fact, you might even notice that some things are actually faster and it can now open HEIF images. How awesome is that?

\n

I'm also working on some 10.13 goodies for Acorn 6 folks later this year. I can't wait to share that with you, but you'll have to wait just a little bit.

\n", + "date_published": "2017-09-25T14:27:27-07:00", + "url": "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html" + }, + { + "id": "http://flyingmeat.com/blog/archives/2018/2/acorn_6.1_is_out.html", + "title": "Acorn 6.1 Is Out", + "content_html": "

Acorn 6.1 has been released.

\n

You can read a longer post about it over on Gus's blog, but the short of it is: Better, faster, smoother, stronger. And now with Metal 2 support.

\n", + "date_published": "2018-02-16T09:59:11-08:00", + }, + { + "id": "http://flyingmeat.com/blog/archives/2018/6/a_pair_of_updates.html", + "title": "A Pair of Updates", + "content_html": "

Happy summer solstice everybody! (at least for folks in the northern hemisphere, and for folks in the south… sorry. It's going to start getting brighter for you though).

\n

Today I've got a pair of minor app updates to annouce for you.

\n

First up is Acorn 6.1.3, which fixes a number of bugs including one that stemmed from trying to use QuickLook on a file that was created with Acorn 1.0. For the one or two of you that this was affecting, hurray!

\n

Next up is Retrobatch, which also includes some bug fixes, the beginnings of Voice Over support, performance improvements, and more.

\n

What's next for these apps? Work on Acorn 6.2 will begin shortly, as will Retrobatch 1.1. WWDC introduced some great new APIs that I want to take advantage of (cool new machine learning things), so that'll be a focus- as well as Dark Mode for Acorn and one other major thing I've got planned. Retrobatch will probably also get the Dark Mode treatment, but not until I've done it for Acorn first.

\n

So it's going to be a busy summer, but I'm looking forward to it.

\n", + "url": "http://flyingmeat.com/blog/archives/2018/6/a_pair_of_updates.html" + } + ] +} \ No newline at end of file diff --git a/api/src/androidTest/java/com/readrops/api/localfeed/json/JSONItemsAdapterTest.kt b/api/src/androidTest/java/com/readrops/api/localfeed/json/JSONItemsAdapterTest.kt new file mode 100644 index 00000000..c45e3e34 --- /dev/null +++ b/api/src/androidTest/java/com/readrops/api/localfeed/json/JSONItemsAdapterTest.kt @@ -0,0 +1,77 @@ +package com.readrops.api.localfeed.json + +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.readrops.api.utils.DateUtils +import com.readrops.api.utils.ParseException +import com.readrops.db.entities.Item +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNotNull +import okio.Buffer +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class JSONItemsAdapterTest { + + private val context: Context = InstrumentationRegistry.getInstrumentation().context + + private val adapter = Moshi.Builder() + .add(Types.newParameterizedType(List::class.java, Item::class.java), JSONItemsAdapter()) + .build() + .adapter>(Types.newParameterizedType(List::class.java, Item::class.java)) + + @Test + fun normalCasesTest() { + val stream = context.resources.assets.open("localfeed/json/json_feed.json") + + val items = adapter.fromJson(Buffer().readFrom(stream))!! + val item = items[0] + + assertEquals(items.size, 10) + assertEquals(item.guid, "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html") + assertEquals(item.title, "Acorn and 10.13") + assertEquals(item.link, "http://flyingmeat.com/blog/archives/2017/9/acorn_and_10.13.html") + assertEquals(item.pubDate, DateUtils.stringToLocalDateTime("2017-09-25T14:27:27-07:00")) + assertEquals(item.author, "Author 1") + assertNotNull(item.content) + } + + @Test + fun otherCasesTest() { + val stream = context.resources.assets.open("localfeed/json/json_items_other_cases.json") + + val item = adapter.fromJson(Buffer().readFrom(stream))!![0] + + assertEquals(item.description, "This is a summary") + assertEquals(item.content, "content_html") + assertEquals(item.imageLink, "https://image.com") + assertEquals(item.author, "Author 1") + } + + @Test + fun nullTitleTest() { + val stream = context.resources.assets.open("localfeed/json/json_items_required_elements.json") + + Assert.assertThrows("Item title is required", ParseException::class.java) { adapter.fromJson(Buffer().readFrom(stream))!![0] } + } + + @Test + fun nullLinkTest() { + val stream = context.resources.assets.open("localfeed/json/json_items_required_elements.json") + + Assert.assertThrows("Item link is required", ParseException::class.java) { adapter.fromJson(Buffer().readFrom(stream))!![1] } + } + + @Test + fun nullDateTest() { + val stream = context.resources.assets.open("localfeed/json/json_items_required_elements.json") + + Assert.assertThrows("Item date is required", ParseException::class.java) { adapter.fromJson(Buffer().readFrom(stream))!![2] } + } +} \ No newline at end of file diff --git a/api/src/main/java/com/readrops/api/localfeed/json/JSONItemsAdapter.kt b/api/src/main/java/com/readrops/api/localfeed/json/JSONItemsAdapter.kt new file mode 100644 index 00000000..b2fbb4f8 --- /dev/null +++ b/api/src/main/java/com/readrops/api/localfeed/json/JSONItemsAdapter.kt @@ -0,0 +1,116 @@ +package com.readrops.api.localfeed.json + +import com.readrops.api.utils.DateUtils +import com.readrops.api.utils.ParseException +import com.readrops.api.utils.nextNullableString +import com.readrops.db.entities.Item +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter + +class JSONItemsAdapter : JsonAdapter>() { + + override fun toJson(writer: JsonWriter, value: List?) { + // not useful + } + + @FromJson + override fun fromJson(reader: JsonReader): List { + try { + val items = arrayListOf() + reader.beginObject() + + while (reader.hasNext()) { + when (reader.nextName()) { + "items" -> parseItems(reader, items) + else -> reader.skipValue() + } + } + + return items + } catch (e: Exception) { + throw ParseException(e.message) + } + } + + private fun parseItems(reader: JsonReader, items: MutableList) { + reader.beginArray() + + while (reader.hasNext()) { + reader.beginObject() + val item = Item() + + var contentText: String? = null + var contentHtml: String? = null + + while (reader.hasNext()) { + with(item) { + when (reader.selectName(names)) { + 0 -> guid = reader.nextString() + 1 -> link = reader.nextString() + 2 -> title = reader.nextString() + 3 -> contentHtml = reader.nextNullableString() + 4 -> contentText = reader.nextNullableString() + 5 -> description = reader.nextNullableString() + 6 -> imageLink = reader.nextNullableString() + 7 -> pubDate = DateUtils.stringToLocalDateTime(reader.nextString()) + 8 -> author = parseAuthor(reader) // jsonfeed 1.0 + 9 -> author = parseAuthors(reader) // jsonfeed 1.1 + } + } + } + + validateItem(item) + item.content = if (contentHtml != null) contentHtml else contentText + + reader.endObject() + items += item + } + + reader.endArray() + } + + private fun parseAuthor(reader: JsonReader): String? { + var author: String? = null + reader.beginObject() + + while (reader.hasNext()) { + when (reader.nextName()) { + "name" -> author = reader.nextNullableString() + else -> reader.skipValue() + } + } + + reader.endObject() + return author + } + + /** + * Returns the first author of the array + */ + private fun parseAuthors(reader: JsonReader): String? { + val authors = arrayListOf() + reader.beginArray() + + while (reader.hasNext()) { + authors.add(parseAuthor(reader)) + } + + reader.endArray() + return if (authors.filterNotNull().isNotEmpty()) authors.filterNotNull().first() else null + } + + private fun validateItem(item: Item) { + when { + item.title == null -> throw ParseException("Item title is required") + item.link == null -> throw ParseException("Item link is required") + item.pubDate == null -> throw ParseException("Item date id required") + } + } + + companion object { + val names: JsonReader.Options = JsonReader.Options.of("id", "url", "title", "content_html", "content_text", + "summary", "image", "date_published", "author", "authors") + } +} \ No newline at end of file