Xml adapters now receive a Konsumer object instead of an input stream

This commit is contained in:
Shinokuni 2021-08-17 21:18:03 +02:00
parent 655ab2bc83
commit 767662df59
12 changed files with 107 additions and 103 deletions

View File

@ -2,9 +2,9 @@ package com.readrops.api.localfeed
import android.accounts.NetworkErrorException
import androidx.annotation.WorkerThread
import com.gitlab.mvysny.konsumexml.Konsumer
import com.gitlab.mvysny.konsumexml.konsumeXml
import com.readrops.api.localfeed.json.JSONFeedAdapter
import com.readrops.api.localfeed.json.JSONItemsAdapter
import com.readrops.api.utils.ApiUtils
import com.readrops.api.utils.AuthInterceptor
import com.readrops.api.utils.exceptions.ParseException
@ -12,7 +12,6 @@ import com.readrops.api.utils.exceptions.UnknownFormatException
import com.readrops.db.entities.Feed
import com.readrops.db.entities.Item
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
@ -20,12 +19,10 @@ import okhttp3.Response
import okio.Buffer
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
class LocalRSSDataSource(private val httpClient: OkHttpClient): KoinComponent {
class LocalRSSDataSource(private val httpClient: OkHttpClient) : KoinComponent {
/**
* Query RSS url
@ -41,34 +38,10 @@ class LocalRSSDataSource(private val httpClient: OkHttpClient): KoinComponent {
return when {
response.isSuccessful -> {
val header = response.header(ApiUtils.CONTENT_TYPE_HEADER)
?: throw UnknownFormatException("Unable to get $url content-type")
val contentType = ApiUtils.parseContentType(header)
?: throw ParseException("Unable to parse $url content-type")
var type = LocalRSSHelper.getRSSType(contentType)
val bodyArray = response.peekBody(Long.MAX_VALUE).bytes()
val konsumer = ByteArrayInputStream(bodyArray).konsumeXml()
// if we can't guess type based on content-type header, we use the content
if (type == LocalRSSHelper.RSSType.UNKNOWN) {
val rootKonsumer = konsumer.nextElement(LocalRSSHelper.RSS_ROOT_NAMES)
if (rootKonsumer != null) {
type = LocalRSSHelper.guessRSSType(rootKonsumer)
konsumer.close()
}
}
// if we can't guess type even with the content, we are unable to go further
if (type == LocalRSSHelper.RSSType.UNKNOWN) throw UnknownFormatException("Unable to guess $url RSS type")
val feed = parseFeed(ByteArrayInputStream(bodyArray), type, response)
val items = parseItems(ByteArrayInputStream(bodyArray), type)
val pair = parseContent(response, url)
response.body?.close()
Pair(feed, items)
pair
}
response.code == HttpURLConnection.HTTP_NOT_MODIFIED -> null
else -> throw NetworkErrorException("$url returned ${response.code} code : ${response.message}")
@ -115,18 +88,54 @@ class LocalRSSDataSource(private val httpClient: OkHttpClient): KoinComponent {
return httpClient.newCall(requestBuilder.build()).execute()
}
private fun parseFeed(stream: InputStream, type: LocalRSSHelper.RSSType, response: Response): Feed {
private fun parseContent(response: Response, url: String): Pair<Feed, List<Item>> {
val header = response.header(ApiUtils.CONTENT_TYPE_HEADER)
?: throw UnknownFormatException("Unable to get $url content-type")
val contentType = ApiUtils.parseContentType(header)
?: throw ParseException("Unable to parse $url content-type")
var type = LocalRSSHelper.getRSSType(contentType)
var konsumer: Konsumer? = null
if (type != LocalRSSHelper.RSSType.JSONFEED)
konsumer = response.body!!.byteStream().konsumeXml()
var rootKonsumer: Konsumer? = null
// if we can't guess type based on content-type header, we use the content
if (type == LocalRSSHelper.RSSType.UNKNOWN) {
konsumer = response.body!!.byteStream().konsumeXml()
rootKonsumer = konsumer.nextElement(LocalRSSHelper.RSS_ROOT_NAMES)
if (rootKonsumer != null) {
type = LocalRSSHelper.guessRSSType(rootKonsumer)
}
}
// if we can't guess type even with the content, we are unable to go further
if (type == LocalRSSHelper.RSSType.UNKNOWN) throw UnknownFormatException("Unable to guess $url RSS type")
val feed = parseFeed(rootKonsumer ?: konsumer, type, response)
//val items = parseItems(ByteArrayInputStream(bodyArray), type)
rootKonsumer?.finish()
konsumer?.close()
return Pair(feed, listOf())
}
private fun parseFeed(konsumer: Konsumer?, type: LocalRSSHelper.RSSType, response: Response): Feed {
val feed = if (type != LocalRSSHelper.RSSType.JSONFEED) {
val adapter = XmlAdapter.xmlFeedAdapterFactory(type)
adapter.fromXml(stream)
adapter.fromXml(konsumer!!)
} else {
val adapter = Moshi.Builder()
.add(JSONFeedAdapter())
.build()
.adapter(Feed::class.java)
adapter.fromJson(Buffer().readFrom(stream))!!
adapter.fromJson(Buffer().readFrom(response.body!!.byteStream()))!!
}
handleSpecialCases(feed, type, response)
@ -137,7 +146,7 @@ class LocalRSSDataSource(private val httpClient: OkHttpClient): KoinComponent {
return feed
}
private fun parseItems(stream: InputStream, type: LocalRSSHelper.RSSType): List<Item> {
/*private fun parseItems(stream: InputStream, type: LocalRSSHelper.RSSType): List<Item> {
return if (type != LocalRSSHelper.RSSType.JSONFEED) {
val adapter = XmlAdapter.xmlItemsAdapterFactory(type)
@ -150,7 +159,7 @@ class LocalRSSDataSource(private val httpClient: OkHttpClient): KoinComponent {
adapter.fromJson(Buffer().readFrom(stream))!!
}
}
}*/
private fun handleSpecialCases(feed: Feed, type: LocalRSSHelper.RSSType, response: Response) {
with(feed) {

View File

@ -1,5 +1,6 @@
package com.readrops.api.localfeed
import com.gitlab.mvysny.konsumexml.Konsumer
import com.readrops.api.localfeed.atom.ATOMFeedAdapter
import com.readrops.api.localfeed.atom.ATOMItemsAdapter
import com.readrops.api.localfeed.rss1.RSS1FeedAdapter
@ -8,11 +9,10 @@ import com.readrops.api.localfeed.rss2.RSS2FeedAdapter
import com.readrops.api.localfeed.rss2.RSS2ItemsAdapter
import com.readrops.db.entities.Feed
import com.readrops.db.entities.Item
import java.io.InputStream
interface XmlAdapter<T> {
fun fromXml(inputStream: InputStream): T
fun fromXml(konsumer: Konsumer): T
companion object {
fun xmlFeedAdapterFactory(type: LocalRSSHelper.RSSType): XmlAdapter<Feed> = when (type) {

View File

@ -3,23 +3,22 @@ package com.readrops.api.localfeed.atom
import com.gitlab.mvysny.konsumexml.Konsumer
import com.gitlab.mvysny.konsumexml.Names
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
import com.gitlab.mvysny.konsumexml.konsumeXml
import com.readrops.api.localfeed.LocalRSSHelper
import com.readrops.api.localfeed.XmlAdapter
import com.readrops.api.utils.exceptions.ParseException
import com.readrops.api.utils.extensions.checkElement
import com.readrops.api.utils.extensions.nonNullText
import com.readrops.api.utils.extensions.nullableText
import com.readrops.db.entities.Feed
import java.io.InputStream
class ATOMFeedAdapter : XmlAdapter<Feed> {
override fun fromXml(inputStream: InputStream): Feed {
val konsume = inputStream.konsumeXml()
override fun fromXml(konsumer: Konsumer): Feed {
val feed = Feed()
return try {
konsume.child("feed") {
allChildrenAutoIgnore(names) {
konsumer.checkElement(LocalRSSHelper.ATOM_ROOT_NAME) {
it.allChildrenAutoIgnore(names) {
with(feed) {
when (tagName) {
"title" -> name = nonNullText()
@ -30,7 +29,7 @@ class ATOMFeedAdapter : XmlAdapter<Feed> {
}
}
konsume.close()
konsumer.close()
feed
} catch (e: Exception) {
throw ParseException(e.message)
@ -38,7 +37,7 @@ class ATOMFeedAdapter : XmlAdapter<Feed> {
}
private fun parseLink(konsume: Konsumer, feed: Feed) = with(konsume) {
val rel = attributes.getValueOpt("rel")
val rel = attributes.getValueOrNull("rel")
if (rel == "self")
feed.url = attributes["href"]

View File

@ -3,7 +3,6 @@ package com.readrops.api.localfeed.atom
import com.gitlab.mvysny.konsumexml.Konsumer
import com.gitlab.mvysny.konsumexml.Names
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
import com.gitlab.mvysny.konsumexml.konsumeXml
import com.readrops.api.localfeed.XmlAdapter
import com.readrops.api.utils.*
import com.readrops.api.utils.exceptions.ParseException
@ -12,12 +11,10 @@ import com.readrops.api.utils.extensions.nullableText
import com.readrops.api.utils.extensions.nullableTextRecursively
import com.readrops.db.entities.Item
import org.joda.time.LocalDateTime
import java.io.InputStream
class ATOMItemsAdapter : XmlAdapter<List<Item>> {
override fun fromXml(inputStream: InputStream): List<Item> {
val konsumer = inputStream.konsumeXml()
override fun fromXml(konsumer: Konsumer): List<Item> {
val items = arrayListOf<Item>()
return try {

View File

@ -1,24 +1,24 @@
package com.readrops.api.localfeed.rss1
import com.gitlab.mvysny.konsumexml.Konsumer
import com.gitlab.mvysny.konsumexml.Names
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
import com.gitlab.mvysny.konsumexml.konsumeXml
import com.readrops.api.localfeed.LocalRSSHelper
import com.readrops.api.localfeed.XmlAdapter
import com.readrops.api.utils.exceptions.ParseException
import com.readrops.api.utils.extensions.checkElement
import com.readrops.api.utils.extensions.nonNullText
import com.readrops.api.utils.extensions.nullableText
import com.readrops.db.entities.Feed
import java.io.InputStream
class RSS1FeedAdapter : XmlAdapter<Feed> {
override fun fromXml(inputStream: InputStream): Feed {
val konsume = inputStream.konsumeXml()
override fun fromXml(konsumer: Konsumer): Feed {
val feed = Feed()
return try {
konsume.child("RDF") {
allChildrenAutoIgnore("channel") {
konsumer.checkElement(LocalRSSHelper.RSS_1_ROOT_NAME) {
it.allChildrenAutoIgnore("channel") {
feed.url = attributes.getValueOpt("about",
namespace = "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
@ -34,7 +34,7 @@ class RSS1FeedAdapter : XmlAdapter<Feed> {
}
}
konsume.close()
konsumer.close()
feed
} catch (e: Exception) {
throw ParseException(e.message)

View File

@ -1,8 +1,8 @@
package com.readrops.api.localfeed.rss1
import com.gitlab.mvysny.konsumexml.Konsumer
import com.gitlab.mvysny.konsumexml.Names
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
import com.gitlab.mvysny.konsumexml.konsumeXml
import com.readrops.api.localfeed.XmlAdapter
import com.readrops.api.localfeed.XmlAdapter.Companion.AUTHORS_MAX
import com.readrops.api.utils.*
@ -12,12 +12,10 @@ import com.readrops.api.utils.extensions.nullableText
import com.readrops.api.utils.extensions.nullableTextRecursively
import com.readrops.db.entities.Item
import org.joda.time.LocalDateTime
import java.io.InputStream
class RSS1ItemsAdapter : XmlAdapter<List<Item>> {
override fun fromXml(inputStream: InputStream): List<Item> {
val konsumer = inputStream.konsumeXml()
override fun fromXml(konsumer: Konsumer): List<Item> {
val items = arrayListOf<Item>()
return try {

View File

@ -1,25 +1,25 @@
package com.readrops.api.localfeed.rss2
import com.gitlab.mvysny.konsumexml.Konsumer
import com.gitlab.mvysny.konsumexml.Names
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
import com.gitlab.mvysny.konsumexml.konsumeXml
import com.readrops.api.localfeed.LocalRSSHelper
import com.readrops.api.localfeed.XmlAdapter
import com.readrops.api.utils.exceptions.ParseException
import com.readrops.api.utils.extensions.checkElement
import com.readrops.api.utils.extensions.nonNullText
import com.readrops.api.utils.extensions.nullableText
import com.readrops.db.entities.Feed
import org.jsoup.Jsoup
import java.io.InputStream
class RSS2FeedAdapter : XmlAdapter<Feed> {
override fun fromXml(inputStream: InputStream): Feed {
val konsume = inputStream.konsumeXml()
override fun fromXml(konsumer: Konsumer): Feed {
val feed = Feed()
return try {
konsume.child("rss") {
child("channel") {
konsumer.checkElement(LocalRSSHelper.RSS_2_ROOT_NAME) {
it.child("channel") {
allChildrenAutoIgnore(names) {
with(feed) {
when (tagName) {
@ -37,7 +37,7 @@ class RSS2FeedAdapter : XmlAdapter<Feed> {
}
}
konsume.close()
konsumer.close()
feed
} catch (e: Exception) {
throw ParseException(e.message)

View File

@ -10,12 +10,10 @@ import com.readrops.api.utils.extensions.nullableText
import com.readrops.api.utils.extensions.nullableTextRecursively
import com.readrops.db.entities.Item
import org.joda.time.LocalDateTime
import java.io.InputStream
class RSS2ItemsAdapter : XmlAdapter<List<Item>> {
override fun fromXml(inputStream: InputStream): List<Item> {
val konsumer = inputStream.konsumeXml()
override fun fromXml(konsumer: Konsumer): List<Item> {
val items = mutableListOf<Item>()
return try {

View File

@ -11,9 +11,11 @@ import com.readrops.api.services.nextcloudnews.adapters.NextNewsUserAdapter;
import com.readrops.api.utils.ApiUtils;
import com.readrops.api.utils.exceptions.ConflictException;
import com.readrops.api.utils.exceptions.UnknownFormatException;
import com.readrops.api.utils.extensions.KonsumerExtensionsKt;
import com.readrops.db.entities.Feed;
import com.readrops.db.entities.Folder;
import com.readrops.db.entities.Item;
import com.readrops.db.entities.account.Account;
import com.readrops.db.pojo.StarItem;
import java.io.IOException;
@ -23,7 +25,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.ResponseBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import retrofit2.Response;
public class NextNewsDataSource {
@ -40,14 +43,16 @@ public class NextNewsDataSource {
}
@Nullable
public String login(String user) throws IOException {
Response<ResponseBody> response = api.getUser(user).execute();
public String login(OkHttpClient client, Account account) throws IOException {
Request request = new Request.Builder()
.url(account.getUrl() + "/ocs/v1.php/cloud/users/" + account.getLogin())
.addHeader("OCS-APIRequest", "true")
.build();
if (!response.isSuccessful()) {
return null;
}
okhttp3.Response response = client.newCall(request).execute();
String displayName = new NextNewsUserAdapter().fromXml(response.body().byteStream());
String displayName = new NextNewsUserAdapter().fromXml(KonsumerExtensionsKt
.instantiateKonsumer(response.body().byteStream()));
response.body().close();
return displayName;
@ -284,7 +289,7 @@ public class NextNewsDataSource {
private int value;
ItemQueryType(int value) {
ItemQueryType(int value) {
this.value = value;
}

View File

@ -1,16 +1,14 @@
package com.readrops.api.services.nextcloudnews.adapters
import com.gitlab.mvysny.konsumexml.Konsumer
import com.gitlab.mvysny.konsumexml.allChildrenAutoIgnore
import com.gitlab.mvysny.konsumexml.konsumeXml
import com.readrops.api.localfeed.XmlAdapter
import com.readrops.api.utils.exceptions.ParseException
import com.readrops.api.utils.extensions.nonNullText
import java.io.InputStream
class NextNewsUserAdapter : XmlAdapter<String> {
override fun fromXml(inputStream: InputStream): String {
val konsumer = inputStream.konsumeXml()
override fun fromXml(konsumer: Konsumer): String {
var displayName: String? = null
return try {

View File

@ -2,8 +2,11 @@ package com.readrops.api.utils.extensions
import com.gitlab.mvysny.konsumexml.Konsumer
import com.gitlab.mvysny.konsumexml.Whitespace
import com.gitlab.mvysny.konsumexml.konsumeXml
import com.gitlab.mvysny.konsumexml.textRecursively
import com.readrops.api.utils.exceptions.ParseException
import java.io.InputStream
import java.util.stream.Stream
fun Konsumer.nonNullText(): String {
val text = text(whitespace = Whitespace.preserve)
@ -25,4 +28,18 @@ fun Konsumer.checkRoot(name: String): Boolean = try {
true
} catch (e: Exception) {
false
}
}
/**
* Check is the element [name] is already loaded, loads it if it not the case and calls [block]
*/
fun Konsumer.checkElement(name: String, block: (Konsumer) -> Unit) {
if (checkRoot(name)) block(this)
else {
child(name) {
block(this)
}
}
}
public fun instantiateKonsumer(stream: InputStream) = stream.konsumeXml()

View File

@ -12,7 +12,6 @@ import com.readrops.api.services.SyncResult;
import com.readrops.api.services.SyncType;
import com.readrops.api.services.nextcloudnews.NextNewsDataSource;
import com.readrops.api.services.nextcloudnews.NextNewsSyncData;
import com.readrops.api.services.nextcloudnews.adapters.NextNewsUserAdapter;
import com.readrops.api.utils.exceptions.UnknownFormatException;
import com.readrops.app.addfeed.FeedInsertionResult;
import com.readrops.app.addfeed.ParsingResult;
@ -37,8 +36,6 @@ import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class NextNewsRepository extends ARepository {
@ -58,22 +55,8 @@ public class NextNewsRepository extends ARepository {
return Single.<String>create(emitter -> {
OkHttpClient httpClient = KoinJavaComponent.get(OkHttpClient.class);
Request request = new Request.Builder()
.url(account.getUrl() + "/ocs/v1.php/cloud/users/" + account.getLogin())
.addHeader("OCS-APIRequest", "true")
.build();
Response response = httpClient.newCall(request).execute();
if (response.isSuccessful()) {
String displayName = new NextNewsUserAdapter().fromXml(response.body().byteStream());
response.body().close();
emitter.onSuccess(displayName);
} else {
// TODO better error handling
emitter.onError(new Exception("Login exception : " + response.code() + " error"));
}
String displayName = dataSource.login(httpClient, account);
emitter.onSuccess(displayName);
}).flatMapCompletable(displayName -> {
account.setDisplayedName(displayName);
account.setCurrentAccount(true);