Skip to content
This repository was archived by the owner on Jul 29, 2022. It is now read-only.

Commit b345189

Browse files
authored
New APIs using coroutines and R2's HttpClient instead of Fuel and kovenant (#55)
Thanks @stevenzeck!
1 parent 9355f52 commit b345189

File tree

5 files changed

+150
-23
lines changed

5 files changed

+150
-23
lines changed

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.
44

55
**Warning:** Features marked as *experimental* may change or be removed in a future release without notice. Use with caution.
66

7-
<!-- ## [Unreleased] -->
7+
## [Unreleased]
8+
9+
### Added
10+
11+
* 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)).
812

913
## [2.0.0]
1014

r2-opds/build.gradle

+1-2
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,10 @@ dependencies {
4343
}
4444

4545
implementation "androidx.appcompat:appcompat:1.3.0-beta1"
46-
implementation "com.github.kittinunf.fuel:fuel-android:2.2.2"
47-
implementation "com.github.kittinunf.fuel:fuel:2.2.2"
4846
implementation "com.jakewharton.timber:timber:4.7.1"
4947
implementation "joda-time:joda-time:2.10.5"
5048
implementation "nl.komponents.kovenant:kovenant:3.3.0"
49+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
5150

5251
testImplementation "junit:junit:4.13.2"
5352
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright 2021 Readium Foundation. All rights reserved.
3+
* Use of this source code is governed by the BSD-style license
4+
* available in the top-level LICENSE file of the project.
5+
*/
6+
7+
package org.readium.r2.opds
8+
9+
import kotlinx.coroutines.runBlocking
10+
import nl.komponents.kovenant.Promise
11+
import nl.komponents.kovenant.task
12+
import org.readium.r2.shared.util.http.HttpClient
13+
import org.readium.r2.shared.util.http.HttpFetchResponse
14+
import org.readium.r2.shared.util.http.HttpRequest
15+
16+
internal fun HttpClient.fetchPromise(request: HttpRequest): Promise<HttpFetchResponse, Exception> {
17+
return task { runBlocking { fetch(request).getOrThrow() } }
18+
}

r2-opds/src/main/java/org/readium/r2/opds/OPDS1Parser.kt

+94-11
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
package org.readium.r2.opds
1111

12-
import com.github.kittinunf.fuel.Fuel
1312
import nl.komponents.kovenant.Promise
1413
import nl.komponents.kovenant.then
1514
import org.joda.time.DateTime
@@ -18,10 +17,14 @@ import org.readium.r2.shared.extensions.toMap
1817
import org.readium.r2.shared.opds.*
1918
import org.readium.r2.shared.parser.xml.ElementNode
2019
import org.readium.r2.shared.parser.xml.XmlParser
21-
import org.readium.r2.shared.promise
2220
import org.readium.r2.shared.publication.*
2321
import org.readium.r2.shared.toJSON
2422
import org.readium.r2.shared.util.Href
23+
import org.readium.r2.shared.util.Try
24+
import org.readium.r2.shared.util.http.DefaultHttpClient
25+
import org.readium.r2.shared.util.http.HttpClient
26+
import org.readium.r2.shared.util.http.HttpRequest
27+
import org.readium.r2.shared.util.http.fetchWithDecoder
2528
import java.net.URL
2629

2730
enum class OPDSParserError {
@@ -45,18 +48,38 @@ object Namespaces {
4548
class OPDS1Parser {
4649
companion object {
4750

51+
suspend fun parseUrlString(url: String, client: HttpClient = DefaultHttpClient()): Try<ParseData, Exception> {
52+
return client.fetchWithDecoder(HttpRequest(url)) {
53+
this.parse(it.body, URL(url))
54+
}
55+
}
56+
57+
suspend fun parseRequest(request: HttpRequest, client: HttpClient = DefaultHttpClient()): Try<ParseData, Exception> {
58+
return client.fetchWithDecoder(request) {
59+
this.parse(it.body, URL(request.url))
60+
}
61+
}
62+
63+
@Deprecated(
64+
"Use `parseRequest` or `parseUrlString` with coroutines instead",
65+
ReplaceWith("OPDS1Parser.parseUrlString(url)"),
66+
DeprecationLevel.WARNING
67+
)
4868
fun parseURL(url: URL): Promise<ParseData, Exception> {
49-
return Fuel.get(url.toString(), null).promise() then {
50-
val (_, _, result) = it
51-
this.parse(xmlData = result, url = url)
69+
return DefaultHttpClient().fetchPromise(HttpRequest(url.toString())) then {
70+
this.parse(xmlData = it.body, url = url)
5271
}
5372
}
5473

74+
@Deprecated(
75+
"Use `parseRequest` or `parseUrlString` with coroutines instead",
76+
ReplaceWith("OPDS1Parser.parseUrlString(url)"),
77+
DeprecationLevel.WARNING
78+
)
5579
@Suppress("unused")
5680
fun parseURL(headers: MutableMap<String,String>, url: URL): Promise<ParseData, Exception> {
57-
return Fuel.get(url.toString(), null).header(headers).promise() then {
58-
val (_, _, result) = it
59-
this.parse(xmlData = result, url = url)
81+
return DefaultHttpClient().fetchPromise(HttpRequest(url = url.toString(), headers = headers)) then {
82+
this.parse(xmlData = it.body, url = url)
6083
}
6184
}
6285

@@ -178,6 +201,67 @@ class OPDS1Parser {
178201
return MimeTypeParameters(type = type, parameters = params)
179202
}
180203

204+
@Suppress("unused")
205+
suspend fun retrieveOpenSearchTemplate(feed: Feed): Try<String?, Exception> {
206+
207+
var openSearchURL: URL? = null
208+
var selfMimeType: String? = null
209+
210+
for (link in feed.links) {
211+
if (link.rels.contains("self")) {
212+
if (link.type != null) {
213+
selfMimeType = link.type
214+
}
215+
} else if (link.rels.contains("search")) {
216+
openSearchURL = URL(link.href)
217+
}
218+
}
219+
220+
val unwrappedURL = openSearchURL?.let {
221+
return@let it
222+
}
223+
224+
return DefaultHttpClient().fetchWithDecoder(HttpRequest(unwrappedURL.toString())) {
225+
226+
val document = XmlParser().parse(it.body.inputStream())
227+
228+
val urls = document.get("Url", Namespaces.Search)
229+
230+
var typeAndProfileMatch: ElementNode? = null
231+
var typeMatch: ElementNode? = null
232+
233+
selfMimeType?.let { s ->
234+
235+
val selfMimeParams = parseMimeType(mimeTypeString = s)
236+
for (url in urls) {
237+
val urlMimeType = url.getAttr("type") ?: continue
238+
val otherMimeParams = parseMimeType(mimeTypeString = urlMimeType)
239+
if (selfMimeParams.type == otherMimeParams.type) {
240+
if (typeMatch == null) {
241+
typeMatch = url
242+
}
243+
if (selfMimeParams.parameters["profile"] == otherMimeParams.parameters["profile"]) {
244+
typeAndProfileMatch = url
245+
break
246+
}
247+
}
248+
}
249+
val match = typeAndProfileMatch ?: (typeMatch ?: urls[0])
250+
val template = match.getAttr("template")
251+
252+
template
253+
254+
}
255+
null
256+
}
257+
258+
}
259+
260+
@Deprecated(
261+
"Use `retrieveOpenSearchTemplate` with coroutines instead",
262+
ReplaceWith("OPDS1Parser.retrieveOpenSearchTemplate(feed)"),
263+
DeprecationLevel.WARNING
264+
)
181265
@Suppress("unused")
182266
fun fetchOpenSearchTemplate(feed: Feed): Promise<String?, Exception> {
183267

@@ -198,10 +282,9 @@ class OPDS1Parser {
198282
return@let it
199283
}
200284

201-
return Fuel.get(unwrappedURL.toString(), null).promise() then {
202-
val (_, _, result) = it
285+
return DefaultHttpClient().fetchPromise(HttpRequest(unwrappedURL.toString())) then {
203286

204-
val document = XmlParser().parse(result.inputStream())
287+
val document = XmlParser().parse(it.body.inputStream())
205288

206289
val urls = document.get("Url", Namespaces.Search)
207290

r2-opds/src/main/java/org/readium/r2/opds/OPDS2Parser.kt

+32-9
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,20 @@
99

1010
package org.readium.r2.opds
1111

12-
import com.github.kittinunf.fuel.Fuel
1312
import nl.komponents.kovenant.Promise
1413
import nl.komponents.kovenant.then
1514
import org.joda.time.DateTime
1615
import org.json.JSONArray
1716
import org.json.JSONObject
1817
import org.readium.r2.shared.opds.*
19-
import org.readium.r2.shared.promise
2018
import org.readium.r2.shared.publication.Link
2119
import org.readium.r2.shared.publication.Manifest
2220
import org.readium.r2.shared.publication.Publication
21+
import org.readium.r2.shared.util.Try
22+
import org.readium.r2.shared.util.http.DefaultHttpClient
23+
import org.readium.r2.shared.util.http.HttpClient
24+
import org.readium.r2.shared.util.http.HttpRequest
25+
import org.readium.r2.shared.util.http.fetchWithDecoder
2326
import java.net.URL
2427

2528
enum class OPDS2ParserError {
@@ -36,18 +39,38 @@ class OPDS2Parser {
3639

3740
private lateinit var feed: Feed
3841

42+
suspend fun parseUrlString(url: String, client: HttpClient = DefaultHttpClient()): Try<ParseData, Exception> {
43+
return client.fetchWithDecoder(HttpRequest(url)) {
44+
this.parse(it.body, URL(url))
45+
}
46+
}
47+
48+
suspend fun parseRequest(request: HttpRequest, client: HttpClient = DefaultHttpClient()): Try<ParseData, Exception> {
49+
return client.fetchWithDecoder(request) {
50+
this.parse(it.body, URL(request.url))
51+
}
52+
}
53+
54+
@Deprecated(
55+
"Use `parseRequest` or `parseUrlString` with coroutines instead",
56+
ReplaceWith("OPDS2Parser.parseUrlString(url)"),
57+
DeprecationLevel.WARNING
58+
)
3959
fun parseURL(url: URL): Promise<ParseData, Exception> {
40-
return Fuel.get(url.toString(), null).promise() then {
41-
val (_, _, result) = it
42-
this.parse(result, url)
60+
return DefaultHttpClient().fetchPromise(HttpRequest(url.toString())) then {
61+
this.parse(it.body, url)
4362
}
4463
}
4564

65+
@Deprecated(
66+
"Use `parseRequest` or `parseUrlString` with coroutines instead",
67+
ReplaceWith("OPDS2Parser.parseUrlString(url)"),
68+
DeprecationLevel.WARNING
69+
)
4670
@Suppress("unused")
47-
fun parseURL(headers:MutableMap<String,String>, url: URL): Promise<ParseData, Exception> {
48-
return Fuel.get(url.toString(), null).header(headers).promise() then {
49-
val (_, _, result) = it
50-
this.parse(result, url)
71+
fun parseURL(headers: MutableMap<String,String>, url: URL): Promise<ParseData, Exception> {
72+
return DefaultHttpClient().fetchPromise(HttpRequest(url = url.toString(), headers = headers)) then {
73+
this.parse(it.body, url)
5174
}
5275
}
5376

0 commit comments

Comments
 (0)