From cfef6c915e8d26abc2d231bae9e5ea3dca0aab20 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Mon, 26 Apr 2021 19:51:53 -0500 Subject: [PATCH 1/3] Replace Fuel and Promises with new HttpRequest and Try --- .../java/org/readium/r2/opds/OPDS1Parser.kt | 95 +++++++++++++++++++ .../java/org/readium/r2/opds/OPDS2Parser.kt | 34 +++++++ 2 files changed, 129 insertions(+) diff --git a/r2-opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt b/r2-opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt index 826edf4..2510f4b 100644 --- a/r2-opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt +++ b/r2-opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt @@ -22,6 +22,10 @@ import org.readium.r2.shared.promise import org.readium.r2.shared.publication.* import org.readium.r2.shared.toJSON import org.readium.r2.shared.util.Href +import org.readium.r2.shared.util.Try +import org.readium.r2.shared.util.http.DefaultHttpClient +import org.readium.r2.shared.util.http.HttpRequest +import org.readium.r2.shared.util.http.fetchWithDecoder import java.net.URL enum class OPDSParserError { @@ -45,6 +49,17 @@ object Namespaces { class OPDS1Parser { companion object { + suspend fun parseUrlString(url: String): Try { + return DefaultHttpClient().fetchWithDecoder(HttpRequest(url)) { + this.parse(it.body, URL(url)) + } + } + + @Deprecated( + "Use `parseUrlString` with coroutines and pass a string for the URL instead", + ReplaceWith("OPDS1Parser.parseUrlString(url)"), + DeprecationLevel.WARNING + ) fun parseURL(url: URL): Promise { return Fuel.get(url.toString(), null).promise() then { val (_, _, result) = it @@ -52,6 +67,25 @@ class OPDS1Parser { } } + suspend fun parseUrlString( + url: String, + headers: MutableMap + ): Try { + return DefaultHttpClient().fetchWithDecoder( + HttpRequest( + url = url, + headers = headers + ) + ) { + this.parse(it.body, URL(url)) + } + } + + @Deprecated( + "Use `parseUrlString` with coroutines and pass a string for the URL instead", + ReplaceWith("OPDS1Parser.parseUrlString(url, headers)"), + DeprecationLevel.WARNING + ) @Suppress("unused") fun parseURL(headers: MutableMap, url: URL): Promise { return Fuel.get(url.toString(), null).header(headers).promise() then { @@ -178,6 +212,67 @@ class OPDS1Parser { return MimeTypeParameters(type = type, parameters = params) } + @Suppress("unused") + suspend fun retrieveOpenSearchTemplate(feed: Feed): Try { + + var openSearchURL: URL? = null + var selfMimeType: String? = null + + for (link in feed.links) { + if (link.rels.contains("self")) { + if (link.type != null) { + selfMimeType = link.type + } + } else if (link.rels.contains("search")) { + openSearchURL = URL(link.href) + } + } + + val unwrappedURL = openSearchURL?.let { + return@let it + } + + return DefaultHttpClient().fetchWithDecoder(HttpRequest(unwrappedURL.toString())) { + + val document = XmlParser().parse(it.body.inputStream()) + + val urls = document.get("Url", Namespaces.Search) + + var typeAndProfileMatch: ElementNode? = null + var typeMatch: ElementNode? = null + + selfMimeType?.let { s -> + + val selfMimeParams = parseMimeType(mimeTypeString = s) + for (url in urls) { + val urlMimeType = url.getAttr("type") ?: continue + val otherMimeParams = parseMimeType(mimeTypeString = urlMimeType) + if (selfMimeParams.type == otherMimeParams.type) { + if (typeMatch == null) { + typeMatch = url + } + if (selfMimeParams.parameters["profile"] == otherMimeParams.parameters["profile"]) { + typeAndProfileMatch = url + break + } + } + } + val match = typeAndProfileMatch ?: (typeMatch ?: urls[0]) + val template = match.getAttr("template") + + template + + } + null + } + + } + + @Deprecated( + "Use `retrieveOpenSearchTemplate` with coroutines instead", + ReplaceWith("OPDS1Parser.retrieveOpenSearchTemplate(feed)"), + DeprecationLevel.WARNING + ) @Suppress("unused") fun fetchOpenSearchTemplate(feed: Feed): Promise { diff --git a/r2-opds/src/main/java/org/readium/r2/opds/OPDS2Parser.kt b/r2-opds/src/main/java/org/readium/r2/opds/OPDS2Parser.kt index 3ca64ba..340f497 100644 --- a/r2-opds/src/main/java/org/readium/r2/opds/OPDS2Parser.kt +++ b/r2-opds/src/main/java/org/readium/r2/opds/OPDS2Parser.kt @@ -20,6 +20,10 @@ import org.readium.r2.shared.promise import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Manifest import org.readium.r2.shared.publication.Publication +import org.readium.r2.shared.util.Try +import org.readium.r2.shared.util.http.DefaultHttpClient +import org.readium.r2.shared.util.http.HttpRequest +import org.readium.r2.shared.util.http.fetchWithDecoder import java.net.URL enum class OPDS2ParserError { @@ -36,6 +40,17 @@ class OPDS2Parser { private lateinit var feed: Feed + suspend fun parseUrlString(url: String): Try { + return DefaultHttpClient().fetchWithDecoder(HttpRequest(url)) { + this.parse(it.body, URL(url)) + } + } + + @Deprecated( + "Use `parseUrlString` with coroutines and pass a string for the URL instead", + ReplaceWith("OPDS2Parser.parseUrlString(url)"), + DeprecationLevel.WARNING + ) fun parseURL(url: URL): Promise { return Fuel.get(url.toString(), null).promise() then { val (_, _, result) = it @@ -43,6 +58,25 @@ class OPDS2Parser { } } + suspend fun parseUrlString( + url: String, + headers: MutableMap + ): Try { + return DefaultHttpClient().fetchWithDecoder( + HttpRequest( + url = url, + headers = headers + ) + ) { + this.parse(it.body, URL(url)) + } + } + + @Deprecated( + "Use `parseUrlString` with coroutines and pass a string for the URL instead", + ReplaceWith("OPDS2Parser.parseUrlString(url, headers)"), + DeprecationLevel.WARNING + ) @Suppress("unused") fun parseURL(headers:MutableMap, url: URL): Promise { return Fuel.get(url.toString(), null).header(headers).promise() then { From b9a90c7ddb156bfef39c33758745531615fc4555 Mon Sep 17 00:00:00 2001 From: Steven Zeck <8315038+stevenzeck@users.noreply.github.com> Date: Tue, 4 May 2021 21:34:46 -0500 Subject: [PATCH 2/3] Make suggested changes - Add an optional HttpClient to each new function - Add a new function that accepts a HttpRequest - Replace existing functions using Fuel to use new Http --- r2-opds/build.gradle | 3 +- .../java/org/readium/r2/opds/Extensions.kt | 18 +++++++ .../java/org/readium/r2/opds/OPDS1Parser.kt | 48 +++++++------------ .../java/org/readium/r2/opds/OPDS2Parser.kt | 45 +++++++---------- 4 files changed, 54 insertions(+), 60 deletions(-) create mode 100644 r2-opds/src/main/java/org/readium/r2/opds/Extensions.kt diff --git a/r2-opds/build.gradle b/r2-opds/build.gradle index 38496b6..4329b4b 100644 --- a/r2-opds/build.gradle +++ b/r2-opds/build.gradle @@ -43,11 +43,10 @@ dependencies { } implementation "androidx.appcompat:appcompat:1.3.0-beta1" - implementation "com.github.kittinunf.fuel:fuel-android:2.2.2" - implementation "com.github.kittinunf.fuel:fuel:2.2.2" implementation "com.jakewharton.timber:timber:4.7.1" implementation "joda-time:joda-time:2.10.5" implementation "nl.komponents.kovenant:kovenant:3.3.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3" testImplementation "junit:junit:4.13.2" testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" diff --git a/r2-opds/src/main/java/org/readium/r2/opds/Extensions.kt b/r2-opds/src/main/java/org/readium/r2/opds/Extensions.kt new file mode 100644 index 0000000..5bb3691 --- /dev/null +++ b/r2-opds/src/main/java/org/readium/r2/opds/Extensions.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2021 Readium Foundation. All rights reserved. + * Use of this source code is governed by the BSD-style license + * available in the top-level LICENSE file of the project. + */ + +package org.readium.r2.opds + +import kotlinx.coroutines.runBlocking +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.task +import org.readium.r2.shared.util.http.HttpClient +import org.readium.r2.shared.util.http.HttpFetchResponse +import org.readium.r2.shared.util.http.HttpRequest + +fun HttpClient.fetchPromise(request: HttpRequest): Promise { + return task { runBlocking { fetch(request).getOrThrow() } } +} \ No newline at end of file diff --git a/r2-opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt b/r2-opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt index 2510f4b..9c35470 100644 --- a/r2-opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt +++ b/r2-opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt @@ -9,7 +9,6 @@ package org.readium.r2.opds -import com.github.kittinunf.fuel.Fuel import nl.komponents.kovenant.Promise import nl.komponents.kovenant.then import org.joda.time.DateTime @@ -18,12 +17,12 @@ import org.readium.r2.shared.extensions.toMap import org.readium.r2.shared.opds.* import org.readium.r2.shared.parser.xml.ElementNode import org.readium.r2.shared.parser.xml.XmlParser -import org.readium.r2.shared.promise import org.readium.r2.shared.publication.* import org.readium.r2.shared.toJSON import org.readium.r2.shared.util.Href import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.http.DefaultHttpClient +import org.readium.r2.shared.util.http.HttpClient import org.readium.r2.shared.util.http.HttpRequest import org.readium.r2.shared.util.http.fetchWithDecoder import java.net.URL @@ -49,48 +48,38 @@ object Namespaces { class OPDS1Parser { companion object { - suspend fun parseUrlString(url: String): Try { - return DefaultHttpClient().fetchWithDecoder(HttpRequest(url)) { + suspend fun parseUrlString(url: String, client: HttpClient = DefaultHttpClient()): Try { + return client.fetchWithDecoder(HttpRequest(url)) { this.parse(it.body, URL(url)) } } + suspend fun parseRequest(request: HttpRequest, client: HttpClient = DefaultHttpClient()): Try { + return client.fetchWithDecoder(request) { + this.parse(it.body, URL(request.url)) + } + } + @Deprecated( - "Use `parseUrlString` with coroutines and pass a string for the URL instead", + "Use `parseRequest` or `parseUrlString` with coroutines instead", ReplaceWith("OPDS1Parser.parseUrlString(url)"), DeprecationLevel.WARNING ) fun parseURL(url: URL): Promise { - return Fuel.get(url.toString(), null).promise() then { - val (_, _, result) = it - this.parse(xmlData = result, url = url) - } - } - - suspend fun parseUrlString( - url: String, - headers: MutableMap - ): Try { - return DefaultHttpClient().fetchWithDecoder( - HttpRequest( - url = url, - headers = headers - ) - ) { - this.parse(it.body, URL(url)) + return DefaultHttpClient().fetchPromise(HttpRequest(url.toString())) then { + this.parse(xmlData = it.body, url = url) } } @Deprecated( - "Use `parseUrlString` with coroutines and pass a string for the URL instead", - ReplaceWith("OPDS1Parser.parseUrlString(url, headers)"), + "Use `parseRequest` or `parseUrlString` with coroutines instead", + ReplaceWith("OPDS1Parser.parseUrlString(url)"), DeprecationLevel.WARNING ) @Suppress("unused") fun parseURL(headers: MutableMap, url: URL): Promise { - return Fuel.get(url.toString(), null).header(headers).promise() then { - val (_, _, result) = it - this.parse(xmlData = result, url = url) + return DefaultHttpClient().fetchPromise(HttpRequest(url = url.toString(), headers = headers)) then { + this.parse(xmlData = it.body, url = url) } } @@ -293,10 +282,9 @@ class OPDS1Parser { return@let it } - return Fuel.get(unwrappedURL.toString(), null).promise() then { - val (_, _, result) = it + return DefaultHttpClient().fetchPromise(HttpRequest(unwrappedURL.toString())) then { - val document = XmlParser().parse(result.inputStream()) + val document = XmlParser().parse(it.body.inputStream()) val urls = document.get("Url", Namespaces.Search) diff --git a/r2-opds/src/main/java/org/readium/r2/opds/OPDS2Parser.kt b/r2-opds/src/main/java/org/readium/r2/opds/OPDS2Parser.kt index 340f497..21a0247 100644 --- a/r2-opds/src/main/java/org/readium/r2/opds/OPDS2Parser.kt +++ b/r2-opds/src/main/java/org/readium/r2/opds/OPDS2Parser.kt @@ -9,19 +9,18 @@ package org.readium.r2.opds -import com.github.kittinunf.fuel.Fuel import nl.komponents.kovenant.Promise import nl.komponents.kovenant.then import org.joda.time.DateTime import org.json.JSONArray import org.json.JSONObject import org.readium.r2.shared.opds.* -import org.readium.r2.shared.promise import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Manifest import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.http.DefaultHttpClient +import org.readium.r2.shared.util.http.HttpClient import org.readium.r2.shared.util.http.HttpRequest import org.readium.r2.shared.util.http.fetchWithDecoder import java.net.URL @@ -40,48 +39,38 @@ class OPDS2Parser { private lateinit var feed: Feed - suspend fun parseUrlString(url: String): Try { - return DefaultHttpClient().fetchWithDecoder(HttpRequest(url)) { + suspend fun parseUrlString(url: String, client: HttpClient = DefaultHttpClient()): Try { + return client.fetchWithDecoder(HttpRequest(url)) { this.parse(it.body, URL(url)) } } + suspend fun parseRequest(request: HttpRequest, client: HttpClient = DefaultHttpClient()): Try { + return client.fetchWithDecoder(request) { + this.parse(it.body, URL(request.url)) + } + } + @Deprecated( - "Use `parseUrlString` with coroutines and pass a string for the URL instead", + "Use `parseRequest` or `parseUrlString` with coroutines instead", ReplaceWith("OPDS2Parser.parseUrlString(url)"), DeprecationLevel.WARNING ) fun parseURL(url: URL): Promise { - return Fuel.get(url.toString(), null).promise() then { - val (_, _, result) = it - this.parse(result, url) - } - } - - suspend fun parseUrlString( - url: String, - headers: MutableMap - ): Try { - return DefaultHttpClient().fetchWithDecoder( - HttpRequest( - url = url, - headers = headers - ) - ) { - this.parse(it.body, URL(url)) + return DefaultHttpClient().fetchPromise(HttpRequest(url.toString())) then { + this.parse(it.body, url) } } @Deprecated( - "Use `parseUrlString` with coroutines and pass a string for the URL instead", - ReplaceWith("OPDS2Parser.parseUrlString(url, headers)"), + "Use `parseRequest` or `parseUrlString` with coroutines instead", + ReplaceWith("OPDS2Parser.parseUrlString(url)"), DeprecationLevel.WARNING ) @Suppress("unused") - fun parseURL(headers:MutableMap, url: URL): Promise { - return Fuel.get(url.toString(), null).header(headers).promise() then { - val (_, _, result) = it - this.parse(result, url) + fun parseURL(headers: MutableMap, url: URL): Promise { + return DefaultHttpClient().fetchPromise(HttpRequest(url = url.toString(), headers = headers)) then { + this.parse(it.body, url) } } From 16d5ac6f14f027d24954400f9a14f9b67c09b3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Fri, 7 May 2021 12:33:19 +0200 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 6 +++++- r2-opds/src/main/java/org/readium/r2/opds/Extensions.kt | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0113c8..da676fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file. **Warning:** Features marked as *experimental* may change or be removed in a future release without notice. Use with caution. - +## [Unreleased] + +### Added + +* New APIs using coroutines and R2's `HttpClient` instead of Fuel and kovenant (contributed by [@stevenzeck](https://github.com/readium/r2-opds-kotlin/pull/55)). ## [2.0.0] diff --git a/r2-opds/src/main/java/org/readium/r2/opds/Extensions.kt b/r2-opds/src/main/java/org/readium/r2/opds/Extensions.kt index 5bb3691..7df17db 100644 --- a/r2-opds/src/main/java/org/readium/r2/opds/Extensions.kt +++ b/r2-opds/src/main/java/org/readium/r2/opds/Extensions.kt @@ -13,6 +13,6 @@ import org.readium.r2.shared.util.http.HttpClient import org.readium.r2.shared.util.http.HttpFetchResponse import org.readium.r2.shared.util.http.HttpRequest -fun HttpClient.fetchPromise(request: HttpRequest): Promise { +internal fun HttpClient.fetchPromise(request: HttpRequest): Promise { return task { runBlocking { fetch(request).getOrThrow() } } } \ No newline at end of file