From 2309e8a125dbc0159882333d9d948ac8b572a1a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Menu?= Date: Tue, 20 Apr 2021 15:13:32 +0200 Subject: [PATCH] Add support for the HttpClient (#148) `Streamer` takes a new optional `HttpClient` dependency to handle HTTP requests. --- CHANGELOG.md | 7 ++- .../java/org/readium/r2/streamer/Streamer.kt | 9 ++- .../parser/readium/ReadiumWebPubParser.kt | 58 ++++++++++--------- .../parser/epub/EpubPositionsServiceTest.kt | 2 +- 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d944560..fed91fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,12 @@ 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 + +* `Streamer` takes a new optional `HttpClient` dependency to handle HTTP requests. + ## [2.0.0-beta.2] diff --git a/r2-streamer/src/main/java/org/readium/r2/streamer/Streamer.kt b/r2-streamer/src/main/java/org/readium/r2/streamer/Streamer.kt index 17c4a82d..2724f86d 100644 --- a/r2-streamer/src/main/java/org/readium/r2/streamer/Streamer.kt +++ b/r2-streamer/src/main/java/org/readium/r2/streamer/Streamer.kt @@ -18,6 +18,7 @@ import org.readium.r2.shared.publication.asset.PublicationAsset import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.archive.ArchiveFactory import org.readium.r2.shared.util.archive.DefaultArchiveFactory +import org.readium.r2.shared.util.http.DefaultHttpClient import org.readium.r2.shared.util.logging.WarningLogger import org.readium.r2.shared.util.mediatype.MediaType import org.readium.r2.shared.util.pdf.PdfDocumentFactory @@ -28,7 +29,7 @@ import org.readium.r2.streamer.parser.image.ImageParser import org.readium.r2.streamer.parser.pdf.PdfParser import org.readium.r2.streamer.parser.pdf.PdfiumPdfDocumentFactory import org.readium.r2.streamer.parser.readium.ReadiumWebPubParser -import java.lang.Exception +import kotlin.Exception internal typealias PublicationTry = Try @@ -45,6 +46,7 @@ internal typealias PublicationTry = Try = emptyList(), private val archiveFactory: ArchiveFactory = DefaultArchiveFactory(), private val pdfFactory: PdfDocumentFactory = DefaultPdfDocumentFactory(context), + private val httpClient: DefaultHttpClient = DefaultHttpClient(), private val onCreatePublication: Publication.Builder.() -> Unit = {} ) { @@ -121,7 +124,7 @@ class Streamer constructor( } catch (e: Exception) { throw Publication.OpeningException.ParsingFailed(e) } - } ?: throw Publication.OpeningException.UnsupportedFormat + } ?: throw Publication.OpeningException.UnsupportedFormat(Exception("Cannot find a parser for this asset")) // Transform from the Content Protection. protectedAsset?.let { builder.apply(it.onCreatePublication) } @@ -145,7 +148,7 @@ class Streamer constructor( listOf( EpubParser(), PdfParser(context, pdfFactory), - ReadiumWebPubParser(pdfFactory), + ReadiumWebPubParser(pdfFactory, httpClient), ImageParser(), AudioParser() ) diff --git a/r2-streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt b/r2-streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt index 82697516..055342a9 100644 --- a/r2-streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt +++ b/r2-streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt @@ -9,15 +9,10 @@ package org.readium.r2.streamer.parser.readium -import android.content.Context import kotlinx.coroutines.runBlocking -import org.json.JSONObject import org.readium.r2.shared.PdfSupport import org.readium.r2.shared.drm.DRM -import org.readium.r2.shared.fetcher.ArchiveFetcher -import org.readium.r2.shared.fetcher.Fetcher -import org.readium.r2.shared.fetcher.FileFetcher -import org.readium.r2.shared.fetcher.TransformingFetcher +import org.readium.r2.shared.fetcher.* import org.readium.r2.shared.publication.Manifest import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.publication.asset.FileAsset @@ -25,13 +20,14 @@ import org.readium.r2.shared.publication.asset.PublicationAsset import org.readium.r2.shared.publication.services.PerResourcePositionsService import org.readium.r2.shared.publication.services.locatorServiceFactory import org.readium.r2.shared.publication.services.positionsServiceFactory +import org.readium.r2.shared.util.http.HttpClient import org.readium.r2.shared.util.logging.WarningLogger import org.readium.r2.shared.util.mediatype.MediaType import org.readium.r2.shared.util.pdf.PdfDocumentFactory -import org.readium.r2.streamer.DefaultPdfDocumentFactory import org.readium.r2.streamer.PublicationParser import org.readium.r2.streamer.container.ContainerError import org.readium.r2.streamer.container.PublicationContainer +import org.readium.r2.streamer.extensions.readAsJsonOrNull import org.readium.r2.streamer.fetcher.LcpDecryptor import org.readium.r2.streamer.parser.PubBox import org.readium.r2.streamer.parser.audio.AudioLocatorService @@ -43,38 +39,46 @@ import java.io.FileNotFoundException * Parses any Readium Web Publication package or manifest, e.g. WebPub, Audiobook, DiViNa, LCPDF... */ @OptIn(PdfSupport::class) -class ReadiumWebPubParser(private val pdfFactory: PdfDocumentFactory? = null) : PublicationParser, org.readium.r2.streamer.parser.PublicationParser { - - constructor(context: Context) : this(pdfFactory = DefaultPdfDocumentFactory(context)) +class ReadiumWebPubParser( + private val pdfFactory: PdfDocumentFactory?, + private val httpClient: HttpClient, +) : PublicationParser, org.readium.r2.streamer.parser.PublicationParser { override suspend fun parse( asset: PublicationAsset, fetcher: Fetcher, warnings: WarningLogger? ): Publication.Builder? { + val mediaType = asset.mediaType() - if (!asset.mediaType().isReadiumWebPubProfile) + if (!mediaType.isReadiumWebPubProfile) return null - val manifest = - if (asset.mediaType().isRwpm) { - val manifestLink = fetcher.links().firstOrNull() - ?: error("Empty fetcher.") - val manifestJson = fetcher.get(manifestLink).use { - it.readAsString().getOrThrow() - } - Manifest.fromJSON(JSONObject(manifestJson)) + val isPackage = !mediaType.isRwpm + + val manifestJson = + if (isPackage) { + fetcher.readAsJsonOrNull("/manifest.json") } else { - val manifestLink = fetcher.links() - .firstOrNull { it.href == "/manifest.json" } - ?: error("Unable to find a manifest link.") - val manifestJson = fetcher.get(manifestLink).use { - it.readAsString().getOrThrow() - } - Manifest.fromJSON(JSONObject(manifestJson), packaged = true) + // For a single manifest file, reads the first (and only) file in the fetcher. + fetcher.links().firstOrNull() + ?.let { fetcher.readAsJsonOrNull(it.href) } } - ?: throw Exception("Failed to parse RWPM.") + ?: throw Exception("Manifest not found") + val manifest = Manifest.fromJSON(manifestJson, packaged = isPackage) + ?: throw Exception("Failed to parse the RWPM Manifest") + + @Suppress("NAME_SHADOWING") + var fetcher = fetcher + + // For a manifest, we discard the [fetcher] provided by the Streamer, because it was only + // used to read the manifest file. We use an [HttpFetcher] instead to serve the remote + // resources. + if (!isPackage) { + val baseUrl = manifest.linkWithRel("self")?.let { File(it.href).parent } + fetcher = HttpFetcher(httpClient, baseUrl) + } // Checks the requirements from the LCPDF specification. // https://readium.org/lcp-specs/notes/lcp-for-pdf.html diff --git a/r2-streamer/src/test/java/org/readium/r2/streamer/parser/epub/EpubPositionsServiceTest.kt b/r2-streamer/src/test/java/org/readium/r2/streamer/parser/epub/EpubPositionsServiceTest.kt index 8fe0a5d8..0644580f 100644 --- a/r2-streamer/src/test/java/org/readium/r2/streamer/parser/epub/EpubPositionsServiceTest.kt +++ b/r2-streamer/src/test/java/org/readium/r2/streamer/parser/epub/EpubPositionsServiceTest.kt @@ -426,7 +426,7 @@ class EpubPositionsServiceTest { override suspend fun length() = findResource(link.href) ?.let { Try.success(it.first) } - ?: Try.failure(Resource.Exception.NotFound) + ?: Try.failure(Resource.Exception.NotFound()) override suspend fun read(range: LongRange?): ResourceTry = Try.success(ByteArray(0))