Skip to content

Commit

Permalink
Simplify the PublicationOpener by adding a `DefaultPublicationParse…
Browse files Browse the repository at this point in the history
…r` (readium#433)
  • Loading branch information
mickael-menu authored Jan 9, 2024
1 parent 94463c7 commit 2f04d5e
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 81 deletions.
13 changes: 0 additions & 13 deletions readium/shared/src/main/java/org/readium/r2/shared/OptIn.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,6 @@ public annotation class ExperimentalReadiumApi
)
public annotation class DelicateReadiumApi

@RequiresOptIn(
level = RequiresOptIn.Level.WARNING,
message = "Support for PDF is still experimental. The API may be changed in the future without notice."
)
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.PROPERTY
)
public annotation class PdfSupport

@RequiresOptIn(
level = RequiresOptIn.Level.WARNING,
message = "Support for SearchService is still experimental. The API may be changed in the future without notice."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,29 @@

package org.readium.r2.streamer

import android.content.Context
import org.readium.r2.shared.PdfSupport
import org.readium.r2.shared.publication.Publication
import org.readium.r2.shared.publication.protection.ContentProtection
import org.readium.r2.shared.publication.protection.FallbackContentProtection
import org.readium.r2.shared.util.DebugError
import org.readium.r2.shared.util.Error
import org.readium.r2.shared.util.Try
import org.readium.r2.shared.util.asset.Asset
import org.readium.r2.shared.util.asset.AssetOpener
import org.readium.r2.shared.util.data.ReadError
import org.readium.r2.shared.util.getOrElse
import org.readium.r2.shared.util.http.HttpClient
import org.readium.r2.shared.util.logging.WarningLogger
import org.readium.r2.shared.util.pdf.PdfDocumentFactory
import org.readium.r2.streamer.parser.PublicationParser
import org.readium.r2.streamer.parser.audio.AudioParser
import org.readium.r2.streamer.parser.epub.EpubParser
import org.readium.r2.streamer.parser.image.ImageParser
import org.readium.r2.streamer.parser.pdf.PdfParser
import org.readium.r2.streamer.parser.readium.ReadiumWebPubParser

/**
* Opens a Publication using a list of parsers.
* Opens a [Publication] from an [Asset].
*
* The [PublicationOpener] is configured to use Readium's default parsers, which you can bypass
* using ignoreDefaultParsers. However, you can provide additional [parsers] which will take
* precedence over the default ones. This can also be used to provide an alternative configuration
* of a default parser.
*
* @param context Application context.
* @param parsers Parsers used to open a publication, in addition to the default parsers.
* @param ignoreDefaultParsers When true, only parsers provided in parsers will be used.
* @param parser Parses the content of a publication [Asset].
* @param contentProtections Opens DRM-protected publications.
* @param httpClient Service performing HTTP requests.
* @param pdfFactory Parses a PDF document, optionally protected by password.
* @param assetOpener Opens assets in case of indirection.
* @param onCreatePublication Called on every parsed [Publication.Builder]. It can be used to modify
* the manifest, the root container or the list of service factories of a [Publication].
*/
@OptIn(PdfSupport::class)
public class PublicationOpener(
context: Context,
parsers: List<PublicationParser> = emptyList(),
ignoreDefaultParsers: Boolean = false,
private val parser: PublicationParser,
contentProtections: List<ContentProtection>,
private val httpClient: HttpClient,
pdfFactory: PdfDocumentFactory<*>?,
assetOpener: AssetOpener,
private val onCreatePublication: Publication.Builder.() -> Unit = {}
) {
public sealed class OpenError(
Expand All @@ -74,18 +48,6 @@ public class PublicationOpener(
private val contentProtections: List<ContentProtection> =
contentProtections + FallbackContentProtection()

private val defaultParsers: List<PublicationParser> =
listOfNotNull(
EpubParser(),
pdfFactory?.let { PdfParser(context, it) },
ReadiumWebPubParser(context, httpClient, pdfFactory),
ImageParser(assetOpener.assetSniffer),
AudioParser(assetOpener.assetSniffer)
)

private val parsers: List<PublicationParser> = parsers +
if (!ignoreDefaultParsers) defaultParsers else emptyList()

/**
* Opens a [Publication] from the given asset.
*
Expand Down Expand Up @@ -139,7 +101,7 @@ public class PublicationOpener(
}
}

val builder = parse(transformedAsset, warnings)
val builder = parser.parse(transformedAsset, warnings)
.getOrElse { return Try.failure(wrapParserException(it)) }

builder.apply {
Expand All @@ -152,22 +114,6 @@ public class PublicationOpener(
return Try.success(publication)
}

private suspend fun parse(
publicationAsset: Asset,
warnings: WarningLogger?
): Try<Publication.Builder, PublicationParser.ParseError> {
for (parser in parsers) {
val result = parser.parse(publicationAsset, warnings)
if (
result is Try.Success ||
result is Try.Failure && result.value !is PublicationParser.ParseError.FormatNotSupported
) {
return result
}
}
return Try.failure(PublicationParser.ParseError.FormatNotSupported())
}

private fun wrapParserException(e: PublicationParser.ParseError): OpenError =
when (e) {
is PublicationParser.ParseError.FormatNotSupported ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@

package org.readium.r2.streamer.parser

import android.content.Context
import org.readium.r2.shared.publication.Publication
import org.readium.r2.shared.util.Try
import org.readium.r2.shared.util.asset.Asset
import org.readium.r2.shared.util.asset.AssetOpener
import org.readium.r2.shared.util.http.HttpClient
import org.readium.r2.shared.util.logging.WarningLogger
import org.readium.r2.shared.util.pdf.PdfDocumentFactory
import org.readium.r2.streamer.parser.audio.AudioParser
import org.readium.r2.streamer.parser.epub.EpubParser
import org.readium.r2.streamer.parser.image.ImageParser
import org.readium.r2.streamer.parser.pdf.PdfParser
import org.readium.r2.streamer.parser.readium.ReadiumWebPubParser

/**
* Parses a Publication from an asset.
* Parses a [Publication] from an [Asset].
*/
public interface PublicationParser {

Expand Down Expand Up @@ -41,3 +50,56 @@ public interface PublicationParser {
ParseError("An error occurred while trying to read asset.", cause)
}
}

/**
* Default implementation of [PublicationParser] handling all the publication formats supported by
* Readium.
*
* @param additionalParsers Parsers used to open a publication, in addition to the default parsers. They take precedence over the default ones.
* @param httpClient Service performing HTTP requests.
* @param pdfFactory Parses a PDF document, optionally protected by password.
* @param assetOpener Opens assets in case of indirection.
*/
public class DefaultPublicationParser(
context: Context,
additionalParsers: List<PublicationParser> = emptyList(),
private val httpClient: HttpClient,
pdfFactory: PdfDocumentFactory<*>?,
assetOpener: AssetOpener
) : PublicationParser by CompositePublicationParser(
additionalParsers + listOfNotNull(
EpubParser(),
pdfFactory?.let { PdfParser(context, it) },
ReadiumWebPubParser(context, httpClient, pdfFactory),
ImageParser(assetOpener.assetSniffer),
AudioParser(assetOpener.assetSniffer)
)
)

/**
* A composite [PublicationParser] which tries several parsers until it finds one which supports
* the asset.
*/
public class CompositePublicationParser(
private val parsers: List<PublicationParser>
) : PublicationParser {

public constructor(vararg parsers: PublicationParser) :
this(parsers.toList())

override suspend fun parse(
asset: Asset,
warnings: WarningLogger?
): Try<Publication.Builder, PublicationParser.ParseError> {
for (parser in parsers) {
val result = parser.parse(asset, warnings)
if (
result is Try.Success ||
result is Try.Failure && result.value !is PublicationParser.ParseError.FormatNotSupported
) {
return result
}
}
return Try.failure(PublicationParser.ParseError.FormatNotSupported())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package org.readium.r2.streamer.parser.pdf

import android.content.Context
import org.readium.r2.shared.ExperimentalReadiumApi
import org.readium.r2.shared.PdfSupport
import org.readium.r2.shared.publication.*
import org.readium.r2.shared.publication.services.InMemoryCacheService
import org.readium.r2.shared.publication.services.InMemoryCoverService
Expand All @@ -27,7 +26,6 @@ import org.readium.r2.streamer.parser.PublicationParser
/**
* Parses a PDF file into a Readium [Publication].
*/
@PdfSupport
@OptIn(ExperimentalReadiumApi::class)
public class PdfParser(
context: Context,
Expand Down
17 changes: 10 additions & 7 deletions test-app/src/main/java/org/readium/r2/testapp/Readium.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.readium.r2.shared.util.http.HttpResourceFactory
import org.readium.r2.shared.util.resource.CompositeResourceFactory
import org.readium.r2.shared.util.zip.ZipArchiveOpener
import org.readium.r2.streamer.PublicationOpener
import org.readium.r2.streamer.parser.DefaultPublicationParser

/**
* Holds the shared Readium objects and services used by the app.
Expand Down Expand Up @@ -70,15 +71,17 @@ class Readium(context: Context) {
)

/**
* The PublicationFactory is used to parse and open publications.
* The PublicationFactory is used to open publications.
*/
val publicationOpener = PublicationOpener(
context,
contentProtections = contentProtections,
assetOpener = assetOpener,
httpClient = httpClient,
// Only required if you want to support PDF files using the PDFium adapter.
pdfFactory = PdfiumDocumentFactory(context)
parser = DefaultPublicationParser(
context,
assetOpener = assetOpener,
httpClient = httpClient,
// Only required if you want to support PDF files using the PDFium adapter.
pdfFactory = PdfiumDocumentFactory(context)
),
contentProtections = contentProtections
)

fun onLcpDialogAuthenticationParentAttached(view: View) {
Expand Down

0 comments on commit 2f04d5e

Please sign in to comment.