diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtection.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtection.kt index 4038bb1d67..ed6646c877 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtection.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtection.kt @@ -28,9 +28,7 @@ import org.readium.r2.shared.util.data.decodeRwpm import org.readium.r2.shared.util.data.decodeXml import org.readium.r2.shared.util.data.readDecodeOrElse import org.readium.r2.shared.util.flatMap -import org.readium.r2.shared.util.format.EpubSpecification -import org.readium.r2.shared.util.format.LcpLicenseSpecification -import org.readium.r2.shared.util.format.LcpSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.resource.Resource import org.readium.r2.shared.util.resource.TransformingContainer @@ -57,7 +55,7 @@ internal class LcpContentProtection( allowUserInteraction: Boolean ): Try { if ( - !asset.format.conformsTo(LcpSpecification) + !asset.format.conformsTo(Specification.Lcp) ) { return Try.failure(ContentProtection.OpenError.AssetNotSupported()) } @@ -91,7 +89,7 @@ internal class LcpContentProtection( val encryptionData = when { - asset.format.conformsTo(EpubSpecification) -> parseEncryptionDataEpub( + asset.format.conformsTo(Specification.Epub) -> parseEncryptionDataEpub( asset.container ) else -> parseEncryptionDataRpf(asset.container) @@ -151,7 +149,7 @@ internal class LcpContentProtection( credentials: String?, allowUserInteraction: Boolean ): Try { - if (!licenseAsset.format.conformsTo(LcpLicenseSpecification)) { + if (!licenseAsset.format.conformsTo(Specification.LcpLicense)) { return Try.failure(ContentProtection.OpenError.AssetNotSupported()) } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LicenseContainer.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LicenseContainer.kt index b966d1b387..20f4b72b20 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LicenseContainer.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LicenseContainer.kt @@ -19,9 +19,8 @@ import org.readium.r2.shared.util.asset.Asset import org.readium.r2.shared.util.asset.ContainerAsset import org.readium.r2.shared.util.asset.ResourceAsset import org.readium.r2.shared.util.data.Container -import org.readium.r2.shared.util.format.EpubSpecification import org.readium.r2.shared.util.format.FormatSpecification -import org.readium.r2.shared.util.format.LcpLicenseSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.resource.Resource private val LICENSE_IN_EPUB = Url("META-INF/license.lcpl")!! @@ -44,11 +43,11 @@ internal fun createLicenseContainer( formatSpecification: FormatSpecification ): WritableLicenseContainer = when { - formatSpecification.conformsTo(EpubSpecification) -> FileZipLicenseContainer( + formatSpecification.conformsTo(Specification.Epub) -> FileZipLicenseContainer( file.path, LICENSE_IN_EPUB ) - formatSpecification.conformsTo(LcpLicenseSpecification) -> LcplLicenseContainer(file) + formatSpecification.conformsTo(Specification.LcpLicense) -> LcplLicenseContainer(file) // Assuming it's a Readium WebPub package (e.g. audiobook, LCPDF, etc.) as a fallback else -> FileZipLicenseContainer(file.path, LICENSE_IN_RPF) } @@ -70,7 +69,7 @@ internal fun createLicenseContainer( resource: Resource, formatSpecification: FormatSpecification ): LicenseContainer { - if (!formatSpecification.conformsTo(LcpLicenseSpecification)) { + if (!formatSpecification.conformsTo(Specification.LcpLicense)) { throw LcpException(LcpError.Container.OpenFailed) } @@ -88,7 +87,7 @@ internal fun createLicenseContainer( formatSpecification: FormatSpecification ): LicenseContainer { val licensePath = when { - formatSpecification.conformsTo(EpubSpecification) -> LICENSE_IN_EPUB + formatSpecification.conformsTo(Specification.Epub) -> LICENSE_IN_EPUB // Assuming it's a Readium WebPub package (e.g. audiobook, LCPDF, etc.) as a fallback else -> LICENSE_IN_RPF } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesService.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesService.kt index 8773a8cf82..7fbef1e163 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesService.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesService.kt @@ -42,12 +42,10 @@ import org.readium.r2.shared.util.FileExtension import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.asset.Asset import org.readium.r2.shared.util.asset.AssetRetriever -import org.readium.r2.shared.util.format.EpubSpecification import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.FormatHints import org.readium.r2.shared.util.format.FormatSpecification -import org.readium.r2.shared.util.format.LcpSpecification -import org.readium.r2.shared.util.format.ZipSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.mediatype.MediaType import timber.log.Timber @@ -71,14 +69,23 @@ internal class LicensesService( licenseDocument: LicenseDocument, publicationFile: File ): Try { + val hashIsCorrect = licenseDocument.publicationLink.hash + ?.let { publicationFile.checkSha256(it) } + + if (hashIsCorrect == false) { + return Try.failure( + LcpError.Network(Exception("Digest mismatch: download looks corrupted.")) + ) + } + val mediaType = licenseDocument.publicationLink.mediaType val format = assetRetriever.sniffFormat(publicationFile, FormatHints(mediaType)) .getOrElse { Format( specification = FormatSpecification( - ZipSpecification, - EpubSpecification, - LcpSpecification + Specification.Zip, + Specification.Epub, + Specification.Lcp ), mediaType = MediaType.EPUB, fileExtension = FileExtension("epub") @@ -152,13 +159,14 @@ internal class LicensesService( onProgress = onProgress ) - license.publicationLink.hash - ?.takeIf { destination.checkSha256(it) == false } - ?.run { - throw LcpException( - LcpError.Network(Exception("Digest mismatch: download looks corrupted.")) - ) - } + val hashIsCorrect = license.publicationLink.hash + ?.let { destination.checkSha256(it) } + + if (hashIsCorrect == false) { + throw LcpException( + LcpError.Network(Exception("Digest mismatch: download looks corrupted.")) + ) + } val format = assetRetriever.sniffFormat( @@ -179,9 +187,9 @@ internal class LicensesService( is AssetRetriever.RetrieveError.FormatNotSupported -> { Format( specification = FormatSpecification( - ZipSpecification, - EpubSpecification, - LcpSpecification + Specification.Zip, + Specification.Epub, + Specification.Lcp ), mediaType = MediaType.EPUB, fileExtension = FileExtension("epub") diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/protection/FallbackContentProtection.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/protection/FallbackContentProtection.kt index 9e780c5fd4..926fd3391e 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/protection/FallbackContentProtection.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/protection/FallbackContentProtection.kt @@ -14,8 +14,7 @@ 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.ContainerAsset -import org.readium.r2.shared.util.format.AdeptSpecification -import org.readium.r2.shared.util.format.LcpSpecification +import org.readium.r2.shared.util.format.Specification /** * [ContentProtection] implementation used as a fallback when detecting known DRMs @@ -35,9 +34,9 @@ public class FallbackContentProtection : ContentProtection { } val protectionServiceFactory = when { - asset.format.conformsTo(LcpSpecification) -> + asset.format.conformsTo(Specification.Lcp) -> Service.createFactory(Scheme.Lcp, "Readium LCP") - asset.format.conformsTo(AdeptSpecification) -> + asset.format.conformsTo(Specification.Adept) -> Service.createFactory(Scheme.Adept, "Adobe ADEPT") else -> return Try.failure(ContentProtection.OpenError.AssetNotSupported()) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/publication/services/CoverService.kt b/readium/shared/src/main/java/org/readium/r2/shared/publication/services/CoverService.kt index 0239c692e3..afa0d5e681 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/publication/services/CoverService.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/publication/services/CoverService.kt @@ -11,6 +11,8 @@ package org.readium.r2.shared.publication.services import android.graphics.Bitmap import android.util.Size +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.readium.r2.shared.extensions.scaleToFit import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Publication @@ -76,15 +78,16 @@ internal class ResourceCoverService( private val container: Container ) : CoverService { - override suspend fun cover(): Bitmap? { - val resource = container[coverUrl] - ?: return null + override suspend fun cover(): Bitmap? = + withContext(Dispatchers.IO) { + val resource = container[coverUrl] + ?: return@withContext null - return resource - .read() - .flatMap { it.decodeBitmap() } - .getOrNull() - } + return@withContext resource + .read() + .flatMap { it.decodeBitmap() } + .getOrNull() + } companion object { diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/format/Format.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/format/Format.kt index 7343b63bb0..f1696647ec 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/format/Format.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/format/Format.kt @@ -61,91 +61,93 @@ public value class FormatSpecification(public val specifications: Set { - if (format.hasMoreThan(XmlSpecification) || !source.canReadWholeBlob()) { + if (format.hasMoreThan(Specification.Xml) || !source.canReadWholeBlob()) { return Try.success(format) } @@ -87,13 +87,13 @@ public object HtmlSniffer : FormatSniffer { } private val htmlFormat = Format( - specification = FormatSpecification(HtmlSpecification), + specification = FormatSpecification(Specification.Html), fileExtension = FileExtension("html"), mediaType = MediaType.HTML ) private val xhtmlFormat = Format( - specification = FormatSpecification(XmlSpecification, HtmlSpecification), + specification = FormatSpecification(Specification.Xml, Specification.Html), fileExtension = FileExtension("xhtml"), mediaType = MediaType.XHTML ) @@ -126,7 +126,7 @@ public object Opds1Sniffer : FormatSniffer { format: Format, source: Readable ): Try { - if (format.hasMoreThan(XmlSpecification) || !source.canReadWholeBlob()) { + if (format.hasMoreThan(Specification.Xml) || !source.canReadWholeBlob()) { return Try.success(format) } @@ -148,25 +148,25 @@ public object Opds1Sniffer : FormatSniffer { } private val opds1CatalogFormat = Format( - specification = FormatSpecification(XmlSpecification, Opds1CatalogSpecification), + specification = FormatSpecification(Specification.Xml, Specification.Opds1Catalog), mediaType = MediaType.OPDS1, fileExtension = FileExtension("xml") ) private val opds1NavigationFeedFormat = Format( - specification = FormatSpecification(XmlSpecification, Opds1CatalogSpecification), + specification = FormatSpecification(Specification.Xml, Specification.Opds1Catalog), mediaType = MediaType.OPDS1_NAVIGATION_FEED, fileExtension = FileExtension("xml") ) private val opds1AcquisitionFeedFormat = Format( - specification = FormatSpecification(XmlSpecification, Opds1CatalogSpecification), + specification = FormatSpecification(Specification.Xml, Specification.Opds1Catalog), mediaType = MediaType.OPDS1_ACQUISITION_FEED, fileExtension = FileExtension("xml") ) private val opds1EntryFormat = Format( - specification = FormatSpecification(XmlSpecification, Opds1EntrySpecification), + specification = FormatSpecification(Specification.Xml, Specification.Opds1Entry), mediaType = MediaType.OPDS1_ENTRY, fileExtension = FileExtension("xml") ) @@ -201,7 +201,7 @@ public object Opds2Sniffer : FormatSniffer { format: Format, source: Readable ): Try { - if (format.hasMoreThan(JsonSpecification) || !source.canReadWholeBlob()) { + if (format.hasMoreThan(Specification.Json) || !source.canReadWholeBlob()) { return Try.success(format) } @@ -243,19 +243,19 @@ public object Opds2Sniffer : FormatSniffer { } private val opdsAuthenticationFormat = Format( - specification = FormatSpecification(JsonSpecification, OpdsAuthenticationSpecification), + specification = FormatSpecification(Specification.Json, Specification.OpdsAuthentication), mediaType = MediaType.OPDS_AUTHENTICATION, fileExtension = FileExtension("json") ) private val opds2CatalogFormat = Format( - specification = FormatSpecification(JsonSpecification, Opds2CatalogSpecification), + specification = FormatSpecification(Specification.Json, Specification.Opds2Catalog), mediaType = MediaType.OPDS2, fileExtension = FileExtension("json") ) private val opds2PublicationFormat = Format( - specification = FormatSpecification(JsonSpecification, Opds2PublicationSpecification), + specification = FormatSpecification(Specification.Json, Specification.Opds2Publication), mediaType = MediaType.OPDS2_PUBLICATION, fileExtension = FileExtension("json") ) @@ -281,7 +281,7 @@ public object LcpLicenseSniffer : FormatSniffer { source: Readable ): Try { if ( - format.hasMoreThan(JsonSpecification) || + format.hasMoreThan(Specification.Json) || !source.canReadWholeBlob() ) { return Try.success(format) @@ -296,7 +296,7 @@ public object LcpLicenseSniffer : FormatSniffer { } private val lcplFormat = Format( - specification = FormatSpecification(JsonSpecification, LcpLicenseSpecification), + specification = FormatSpecification(Specification.Json, Specification.LcpLicense), mediaType = MediaType.LCP_LICENSE_DOCUMENT, fileExtension = FileExtension("lcpl") ) @@ -313,7 +313,7 @@ public object BitmapSniffer : FormatSniffer { hints.hasMediaType("image/avif") ) { return Format( - specification = FormatSpecification(AvifSpecification), + specification = FormatSpecification(Specification.Avif), mediaType = MediaType.AVIF, fileExtension = FileExtension("avif") ) @@ -323,7 +323,7 @@ public object BitmapSniffer : FormatSniffer { hints.hasMediaType("image/bmp", "image/x-bmp") ) { return Format( - specification = FormatSpecification(BmpSpecification), + specification = FormatSpecification(Specification.Bmp), mediaType = MediaType.BMP, fileExtension = FileExtension("bmp") ) @@ -333,7 +333,7 @@ public object BitmapSniffer : FormatSniffer { hints.hasMediaType("image/gif") ) { return Format( - specification = FormatSpecification(GifSpecification), + specification = FormatSpecification(Specification.Gif), mediaType = MediaType.GIF, fileExtension = FileExtension("gif") ) @@ -343,7 +343,7 @@ public object BitmapSniffer : FormatSniffer { hints.hasMediaType("image/jpeg") ) { return Format( - specification = FormatSpecification(JpegSpecification), + specification = FormatSpecification(Specification.Jpeg), mediaType = MediaType.JPEG, fileExtension = FileExtension("jpg") ) @@ -353,7 +353,7 @@ public object BitmapSniffer : FormatSniffer { hints.hasMediaType("image/jxl") ) { return Format( - specification = FormatSpecification(JxlSpecification), + specification = FormatSpecification(Specification.Jxl), mediaType = MediaType.JXL, fileExtension = FileExtension("jxl") ) @@ -363,7 +363,7 @@ public object BitmapSniffer : FormatSniffer { hints.hasMediaType("image/png") ) { return Format( - specification = FormatSpecification(PngSpecification), + specification = FormatSpecification(Specification.Png), mediaType = MediaType.PNG, fileExtension = FileExtension("png") ) @@ -373,7 +373,7 @@ public object BitmapSniffer : FormatSniffer { hints.hasMediaType("image/tiff", "image/tiff-fx") ) { return Format( - specification = FormatSpecification(TiffSpecification), + specification = FormatSpecification(Specification.Tiff), mediaType = MediaType.TIFF, fileExtension = FileExtension("tiff") ) @@ -383,7 +383,7 @@ public object BitmapSniffer : FormatSniffer { hints.hasMediaType("image/webp") ) { return Format( - specification = FormatSpecification(WebpSpecification), + specification = FormatSpecification(Specification.Webp), mediaType = MediaType.WEBP, fileExtension = FileExtension("webp") ) @@ -400,7 +400,7 @@ public object AudioSniffer : FormatSniffer { hints.hasFileExtension("aac") ) { return Format( - specification = FormatSpecification(AacSpecification), + specification = FormatSpecification(Specification.Aac), mediaType = MediaType.AAC, fileExtension = FileExtension("aac") ) @@ -410,7 +410,7 @@ public object AudioSniffer : FormatSniffer { hints.hasFileExtension("aiff") ) { return Format( - specification = FormatSpecification(AiffSpecification), + specification = FormatSpecification(Specification.Aiff), mediaType = MediaType.AIFF, fileExtension = FileExtension("aiff") ) @@ -420,7 +420,7 @@ public object AudioSniffer : FormatSniffer { hints.hasFileExtension("flac") ) { return Format( - specification = FormatSpecification(FlacSpecification), + specification = FormatSpecification(Specification.Flac), mediaType = MediaType.FLAC, fileExtension = FileExtension("flac") ) @@ -430,7 +430,7 @@ public object AudioSniffer : FormatSniffer { hints.hasFileExtension("m4a", "m4b", "alac") ) { return Format( - specification = FormatSpecification(Mp4Specification), + specification = FormatSpecification(Specification.Mp4), mediaType = MediaType.MP4, fileExtension = FileExtension("m4a") ) @@ -440,7 +440,7 @@ public object AudioSniffer : FormatSniffer { hints.hasFileExtension("mp3") ) { return Format( - specification = FormatSpecification(Mp3Specification), + specification = FormatSpecification(Specification.Mp3), mediaType = MediaType.MP3, fileExtension = FileExtension("mp3") ) @@ -450,7 +450,7 @@ public object AudioSniffer : FormatSniffer { hints.hasFileExtension("ogg", "oga") ) { return Format( - specification = FormatSpecification(OggSpecification), + specification = FormatSpecification(Specification.Ogg), mediaType = MediaType.OGG, fileExtension = FileExtension("oga") ) @@ -460,7 +460,7 @@ public object AudioSniffer : FormatSniffer { hints.hasFileExtension("opus") ) { return Format( - specification = FormatSpecification(OpusSpecification), + specification = FormatSpecification(Specification.Opus), mediaType = MediaType.OPUS, fileExtension = FileExtension("opus") ) @@ -470,7 +470,7 @@ public object AudioSniffer : FormatSniffer { hints.hasFileExtension("wav") ) { return Format( - specification = FormatSpecification(WavSpecification), + specification = FormatSpecification(Specification.Wav), mediaType = MediaType.WAV, fileExtension = FileExtension("wav") ) @@ -480,7 +480,7 @@ public object AudioSniffer : FormatSniffer { hints.hasFileExtension("webm") ) { return Format( - specification = FormatSpecification(WebmSpecification), + specification = FormatSpecification(Specification.Webm), mediaType = MediaType.WEBM_AUDIO, fileExtension = FileExtension("webm") ) @@ -515,7 +515,7 @@ public object RwpmSniffer : FormatSniffer { source: Readable ): Try { if ( - format.hasMoreThan(JsonSpecification) || + format.hasMoreThan(Specification.Json) || !source.canReadWholeBlob() ) { return Try.success(format) @@ -543,19 +543,19 @@ public object RwpmSniffer : FormatSniffer { } private val rwpmFormat = Format( - specification = FormatSpecification(JsonSpecification, RwpmSpecification), + specification = FormatSpecification(Specification.Json, Specification.Rwpm), mediaType = MediaType.READIUM_WEBPUB_MANIFEST, fileExtension = FileExtension("json") ) private val rwpmAudioFormat = Format( - specification = FormatSpecification(JsonSpecification, RwpmSpecification), + specification = FormatSpecification(Specification.Json, Specification.Rwpm), mediaType = MediaType.READIUM_AUDIOBOOK_MANIFEST, fileExtension = FileExtension("json") ) private val rwpmDivinaFormat = Format( - specification = FormatSpecification(JsonSpecification, RwpmSpecification), + specification = FormatSpecification(Specification.Json, Specification.Rwpm), mediaType = MediaType.DIVINA_MANIFEST, fileExtension = FileExtension("json") ) @@ -609,7 +609,7 @@ public object RpfSniffer : FormatSniffer { container: Container ): Try { if ( - format.hasMoreThan(ZipSpecification, RpfSpecification, LcpSpecification) + format.hasMoreThan(Specification.Zip, Specification.Rpf, Specification.Lcp) ) { return Try.success(format) } @@ -635,7 +635,7 @@ public object RpfSniffer : FormatSniffer { } manifest.conformsTo(Publication.Profile.DIVINA) -> { if (isLcpProtected) { - rpfDivinaFormat.addSpecifications(LcpSpecification) + rpfDivinaFormat.addSpecifications(Specification.Lcp) } else { rpfDivinaFormat } @@ -649,7 +649,7 @@ public object RpfSniffer : FormatSniffer { } else -> if (isLcpProtected) { - rpfFormat.addSpecifications(LcpSpecification) + rpfFormat.addSpecifications(Specification.Lcp) } else { rpfFormat } @@ -663,31 +663,31 @@ public object RpfSniffer : FormatSniffer { .any { it.properties.encryption?.scheme == "http://readium.org/2014/01/lcp" } private val rpfFormat = Format( - specification = FormatSpecification(ZipSpecification, RpfSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Rpf), mediaType = MediaType.READIUM_WEBPUB, fileExtension = FileExtension("webpub") ) private val rpfAudioFormat = Format( - specification = FormatSpecification(ZipSpecification, RpfSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Rpf), mediaType = MediaType.READIUM_AUDIOBOOK, fileExtension = FileExtension("audiobook") ) private val rpfDivinaFormat = Format( - specification = FormatSpecification(ZipSpecification, RpfSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Rpf), mediaType = MediaType.DIVINA, fileExtension = FileExtension("divina") ) private val lcpaFormat = Format( - specification = FormatSpecification(ZipSpecification, RpfSpecification, LcpSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Rpf, Specification.Lcp), mediaType = MediaType.LCP_PROTECTED_AUDIOBOOK, fileExtension = FileExtension("lcpa") ) private val lcpdfFormat = Format( - specification = FormatSpecification(ZipSpecification, RpfSpecification, LcpSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Rpf, Specification.Lcp), mediaType = MediaType.LCP_PROTECTED_PDF, fileExtension = FileExtension("lcpdf") ) @@ -700,7 +700,7 @@ public object W3cWpubSniffer : FormatSniffer { format: Format, source: Readable ): Try { - if (format.hasMoreThan(JsonSpecification) || !source.canReadWholeBlob()) { + if (format.hasMoreThan(Specification.Json) || !source.canReadWholeBlob()) { return Try.success(format) } @@ -717,8 +717,8 @@ public object W3cWpubSniffer : FormatSniffer { return Try.success( Format( specification = FormatSpecification( - JsonSpecification, - W3cPubManifestSpecification + Specification.Json, + Specification.W3cPubManifest ), mediaType = MediaType.W3C_WPUB_MANIFEST, fileExtension = FileExtension("json") @@ -753,7 +753,7 @@ public object EpubSniffer : FormatSniffer { format: Format, container: Container ): Try { - if (format.hasMoreThan(ZipSpecification)) { + if (format.hasMoreThan(Specification.Zip)) { return Try.success(format) } @@ -772,7 +772,7 @@ public object EpubSniffer : FormatSniffer { } private val epubFormatSpecification = Format( - specification = FormatSpecification(ZipSpecification, EpubSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Epub), mediaType = MediaType.EPUB, fileExtension = FileExtension("epub") ) @@ -803,7 +803,7 @@ public object LpfSniffer : FormatSniffer { format: Format, container: Container ): Try { - if (format.hasMoreThan(ZipSpecification)) { + if (format.hasMoreThan(Specification.Zip)) { return Try.success(format) } @@ -829,7 +829,7 @@ public object LpfSniffer : FormatSniffer { } private val lpfFormat = Format( - specification = FormatSpecification(ZipSpecification, LpfSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Lpf), mediaType = MediaType.LPF, fileExtension = FileExtension("lpf") ) @@ -852,7 +852,7 @@ public object RarSniffer : FormatSniffer { hints.hasMediaType("application/x-rar-compressed") ) { return Format( - specification = FormatSpecification(RarSpecification), + specification = FormatSpecification(Specification.Rar), mediaType = MediaType.RAR, fileExtension = FileExtension("rar") ) @@ -874,7 +874,7 @@ public object ZipSniffer : FormatSniffer { hints.hasFileExtension("zip") ) { return Format( - specification = FormatSpecification(ZipSpecification), + specification = FormatSpecification(Specification.Zip), mediaType = MediaType.ZIP, fileExtension = FileExtension("zip") ) @@ -945,7 +945,7 @@ public object ArchiveSniffer : FormatSniffer { ) ) { return Format( - specification = FormatSpecification(ZipSpecification, InformalComicSpecification), + specification = FormatSpecification(Specification.Zip, Specification.InformalComic), mediaType = MediaType.CBZ, fileExtension = FileExtension("cbz") ) @@ -957,7 +957,7 @@ public object ArchiveSniffer : FormatSniffer { hints.hasMediaType("application/x-cbr") ) { return Format( - specification = FormatSpecification(RarSpecification, InformalComicSpecification), + specification = FormatSpecification(Specification.Rar, Specification.InformalComic), mediaType = MediaType.CBR, fileExtension = FileExtension("cbr") ) @@ -966,8 +966,8 @@ public object ArchiveSniffer : FormatSniffer { if (hints.hasFileExtension("zab")) { return Format( specification = FormatSpecification( - ZipSpecification, - InformalAudiobookSpecification + Specification.Zip, + Specification.InformalAudiobook ), mediaType = MediaType.ZAB, fileExtension = FileExtension("zab") @@ -981,7 +981,7 @@ public object ArchiveSniffer : FormatSniffer { format: Format, container: Container ): Try { - if (format.hasMoreThan(ZipSpecification, RarSpecification)) { + if (format.hasMoreThan(Specification.Zip, Specification.Rar)) { return Try.success(format) } @@ -1003,14 +1003,14 @@ public object ArchiveSniffer : FormatSniffer { if (archiveContainsOnlyExtensions(cbzExtensions)) { val mediaType = - if (format.conformsTo(RarSpecification)) { + if (format.conformsTo(Specification.Rar)) { MediaType.CBR } else { MediaType.CBZ } val extension = - if (format.conformsTo(RarSpecification)) { + if (format.conformsTo(Specification.Rar)) { FileExtension("cbr") } else { FileExtension("cbz") @@ -1018,7 +1018,7 @@ public object ArchiveSniffer : FormatSniffer { return Try.success( Format( - specification = format.specification + InformalComicSpecification, + specification = format.specification + Specification.InformalComic, mediaType = mediaType, fileExtension = extension ) @@ -1027,14 +1027,14 @@ public object ArchiveSniffer : FormatSniffer { if (archiveContainsOnlyExtensions(zabExtensions)) { val mediaType = - if (format.conformsTo(ZipSpecification)) { + if (format.conformsTo(Specification.Zip)) { MediaType.ZAB } else { format.mediaType } val extension = - if (format.conformsTo(ZipSpecification)) { + if (format.conformsTo(Specification.Zip)) { FileExtension("zab") } else { format.fileExtension @@ -1042,7 +1042,7 @@ public object ArchiveSniffer : FormatSniffer { return Try.success( Format( - specification = format.specification + InformalAudiobookSpecification, + specification = format.specification + Specification.InformalAudiobook, mediaType = mediaType, fileExtension = extension ) @@ -1090,7 +1090,7 @@ public object PdfSniffer : FormatSniffer { } private val pdfFormat = Format( - specification = FormatSpecification(PdfSpecification), + specification = FormatSpecification(Specification.Pdf), mediaType = MediaType.PDF, fileExtension = FileExtension("pdf") ) @@ -1105,7 +1105,7 @@ public object JsonSniffer : FormatSniffer { hints.hasMediaType("application/json") ) { return Format( - specification = FormatSpecification(JsonSpecification), + specification = FormatSpecification(Specification.Json), mediaType = MediaType.JSON, fileExtension = FileExtension("json") ) @@ -1113,7 +1113,10 @@ public object JsonSniffer : FormatSniffer { if (hints.hasMediaType("application/problem+json")) { return Format( - specification = FormatSpecification(JsonSpecification, ProblemDetailsSpecification), + specification = FormatSpecification( + Specification.Json, + Specification.ProblemDetails + ), mediaType = MediaType.JSON_PROBLEM_DETAILS, fileExtension = FileExtension("json") ) @@ -1137,7 +1140,7 @@ public object JsonSniffer : FormatSniffer { )?.let { return Try.success( Format( - specification = FormatSpecification(JsonSpecification), + specification = FormatSpecification(Specification.Json), mediaType = MediaType.JSON, fileExtension = FileExtension("json") ) @@ -1158,15 +1161,15 @@ public object EpubDrmSniffer : FormatSniffer { container: Container ): Try { if ( - !format.conformsTo(EpubSpecification) || - format.conformsTo(AdeptSpecification) || - format.conformsTo(LcpSpecification) + !format.conformsTo(Specification.Epub) || + format.conformsTo(Specification.Adept) || + format.conformsTo(Specification.Lcp) ) { return Try.success(format) } if (RelativeUrl("META-INF/license.lcpl")!! in container) { - return Try.success(format.addSpecifications(LcpSpecification)) + return Try.success(format.addSpecifications(Specification.Lcp)) } val encryptionDocument = container[Url("META-INF/encryption.xml")!!] @@ -1181,14 +1184,14 @@ public object EpubDrmSniffer : FormatSniffer { ?.flatMap { it.get("RetrievalMethod", EpubEncryption.SIG) } ?.any { it.getAttr("URI") == "license.lcpl#/encryption/content_key" } ?.takeIf { it } - ?.let { return Try.success(format.addSpecifications(LcpSpecification)) } + ?.let { return Try.success(format.addSpecifications(Specification.Lcp)) } encryptionDocument ?.get("EncryptedData", EpubEncryption.ENC) ?.flatMap { it.get("KeyInfo", EpubEncryption.SIG) } ?.flatMap { it.get("resource", "http://ns.adobe.com/adept") } ?.takeIf { it.isNotEmpty() } - ?.let { return Try.success(format.addSpecifications(AdeptSpecification)) } + ?.let { return Try.success(format.addSpecifications(Specification.Adept)) } container[Url("META-INF/rights.xml")!!] ?.readDecodeOrElse( @@ -1196,7 +1199,7 @@ public object EpubDrmSniffer : FormatSniffer { recover = { null } ) ?.takeIf { it.namespace == "http://ns.adobe.com/adept" } - ?.let { return Try.success(format.addSpecifications(AdeptSpecification)) } + ?.let { return Try.success(format.addSpecifications(Specification.Adept)) } return Try.success(format) } @@ -1212,7 +1215,7 @@ public object CssSniffer : FormatSniffer { hints.hasMediaType("text/css") ) { return Format( - specification = FormatSpecification(CssSpecification), + specification = FormatSpecification(Specification.Css), mediaType = MediaType.CSS, fileExtension = FileExtension("css") ) @@ -1233,7 +1236,7 @@ public object JavaScriptSniffer : FormatSniffer { hints.hasMediaType("application/javascript") ) { return Format( - specification = FormatSpecification(JavaScriptSpecification), + specification = FormatSpecification(Specification.JavaScript), mediaType = MediaType.JAVASCRIPT, fileExtension = FileExtension("js") ) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/zip/FileZipArchiveProvider.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/zip/FileZipArchiveProvider.kt index 4989b5c5dc..f75d4605c9 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/zip/FileZipArchiveProvider.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/zip/FileZipArchiveProvider.kt @@ -19,7 +19,7 @@ import org.readium.r2.shared.util.data.Container import org.readium.r2.shared.util.data.ReadError import org.readium.r2.shared.util.file.FileSystemError import org.readium.r2.shared.util.format.Format -import org.readium.r2.shared.util.format.ZipSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.resource.Resource /** @@ -54,7 +54,7 @@ internal class FileZipArchiveProvider { format: Format, file: File ): Try, ArchiveOpener.OpenError> { - if (!format.conformsTo(ZipSpecification)) { + if (!format.conformsTo(Specification.Zip)) { return Try.failure( ArchiveOpener.OpenError.FormatNotSupported(format) ) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/zip/StreamingZipArchiveProvider.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/zip/StreamingZipArchiveProvider.kt index cdc63c5864..8bacbd26a5 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/zip/StreamingZipArchiveProvider.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/zip/StreamingZipArchiveProvider.kt @@ -19,7 +19,7 @@ import org.readium.r2.shared.util.data.ReadError import org.readium.r2.shared.util.data.ReadException import org.readium.r2.shared.util.data.Readable import org.readium.r2.shared.util.format.Format -import org.readium.r2.shared.util.format.ZipSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.resource.Resource import org.readium.r2.shared.util.toUrl import org.readium.r2.shared.util.zip.compress.archivers.zip.ZipFile @@ -46,7 +46,7 @@ internal class StreamingZipArchiveProvider { format: Format, source: Readable ): Try, ArchiveOpener.OpenError> { - if (!format.conformsTo(ZipSpecification)) { + if (!format.conformsTo(Specification.Zip)) { return Try.failure( ArchiveOpener.OpenError.FormatNotSupported(format) ) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/zip/ZipArchiveOpener.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/zip/ZipArchiveOpener.kt index 84f4f56c06..a5bc1d7558 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/zip/ZipArchiveOpener.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/zip/ZipArchiveOpener.kt @@ -13,7 +13,7 @@ import org.readium.r2.shared.util.asset.ContainerAsset import org.readium.r2.shared.util.data.Readable import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.FormatSpecification -import org.readium.r2.shared.util.format.ZipSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.mediatype.MediaType import org.readium.r2.shared.util.resource.Resource @@ -44,7 +44,7 @@ public class ZipArchiveOpener : ArchiveOpener { return container.map { ContainerAsset( format = Format( - specification = FormatSpecification(ZipSpecification), + specification = FormatSpecification(Specification.Zip), mediaType = MediaType.ZIP, fileExtension = FileExtension("zip") ), diff --git a/readium/shared/src/test/java/org/readium/r2/shared/util/asset/AssetSnifferTest.kt b/readium/shared/src/test/java/org/readium/r2/shared/util/asset/AssetSnifferTest.kt index 19633a086a..ed773439f7 100644 --- a/readium/shared/src/test/java/org/readium/r2/shared/util/asset/AssetSnifferTest.kt +++ b/readium/shared/src/test/java/org/readium/r2/shared/util/asset/AssetSnifferTest.kt @@ -18,40 +18,10 @@ import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.checkSuccess import org.readium.r2.shared.util.data.EmptyContainer import org.readium.r2.shared.util.file.FileResource -import org.readium.r2.shared.util.format.AvifSpecification -import org.readium.r2.shared.util.format.BmpSpecification -import org.readium.r2.shared.util.format.CssSpecification -import org.readium.r2.shared.util.format.EpubSpecification import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.FormatHints import org.readium.r2.shared.util.format.FormatSpecification -import org.readium.r2.shared.util.format.GifSpecification -import org.readium.r2.shared.util.format.HtmlSpecification -import org.readium.r2.shared.util.format.InformalAudiobookSpecification -import org.readium.r2.shared.util.format.InformalComicSpecification -import org.readium.r2.shared.util.format.JavaScriptSpecification -import org.readium.r2.shared.util.format.JpegSpecification -import org.readium.r2.shared.util.format.JsonSpecification -import org.readium.r2.shared.util.format.JxlSpecification -import org.readium.r2.shared.util.format.LcpLicenseSpecification -import org.readium.r2.shared.util.format.LcpSpecification -import org.readium.r2.shared.util.format.LpfSpecification -import org.readium.r2.shared.util.format.Opds1CatalogSpecification -import org.readium.r2.shared.util.format.Opds1EntrySpecification -import org.readium.r2.shared.util.format.Opds2CatalogSpecification -import org.readium.r2.shared.util.format.Opds2PublicationSpecification -import org.readium.r2.shared.util.format.OpdsAuthenticationSpecification -import org.readium.r2.shared.util.format.PdfSpecification -import org.readium.r2.shared.util.format.PngSpecification -import org.readium.r2.shared.util.format.ProblemDetailsSpecification -import org.readium.r2.shared.util.format.RarSpecification -import org.readium.r2.shared.util.format.RpfSpecification -import org.readium.r2.shared.util.format.RwpmSpecification -import org.readium.r2.shared.util.format.TiffSpecification -import org.readium.r2.shared.util.format.W3cPubManifestSpecification -import org.readium.r2.shared.util.format.WebpSpecification -import org.readium.r2.shared.util.format.XmlSpecification -import org.readium.r2.shared.util.format.ZipSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.mediatype.MediaType import org.readium.r2.shared.util.resource.Resource import org.readium.r2.shared.util.resource.StringResource @@ -83,21 +53,21 @@ class AssetSnifferTest { private val epubFormat = Format( - specification = FormatSpecification(ZipSpecification, EpubSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Epub), mediaType = MediaType.EPUB, fileExtension = FileExtension("epub") ) private val audiobookFormat = Format( - specification = FormatSpecification(ZipSpecification, RpfSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Rpf), mediaType = MediaType.READIUM_AUDIOBOOK, fileExtension = FileExtension("audiobook") ) private val audiobookManifestFormat = Format( - specification = FormatSpecification(JsonSpecification, RwpmSpecification), + specification = FormatSpecification(Specification.Json, Specification.Rwpm), mediaType = MediaType.READIUM_AUDIOBOOK_MANIFEST, fileExtension = FileExtension("json") ) @@ -210,7 +180,7 @@ class AssetSnifferTest { @Test fun `sniff BMP`() = runBlocking { val format = Format( - specification = FormatSpecification(BmpSpecification), + specification = FormatSpecification(Specification.Bmp), mediaType = MediaType.BMP, fileExtension = FileExtension("bmp") ) @@ -224,13 +194,13 @@ class AssetSnifferTest { @Test fun `sniff CBZ`() = runBlocking { val cbzFormat = Format( - specification = FormatSpecification(ZipSpecification, InformalComicSpecification), + specification = FormatSpecification(Specification.Zip, Specification.InformalComic), mediaType = MediaType.CBZ, fileExtension = FileExtension("cbz") ) val cbrFormat = Format( - specification = FormatSpecification(RarSpecification, InformalComicSpecification), + specification = FormatSpecification(Specification.Rar, Specification.InformalComic), mediaType = MediaType.CBR, fileExtension = FileExtension("cbr") ) @@ -260,7 +230,7 @@ class AssetSnifferTest { @Test fun `sniff DiViNa`() = runBlocking { val format = Format( - specification = FormatSpecification(ZipSpecification, RpfSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Rpf), mediaType = MediaType.DIVINA, fileExtension = FileExtension("divina") ) @@ -282,7 +252,7 @@ class AssetSnifferTest { @Test fun `sniff DiViNa manifest`() = runBlocking { val format = Format( - specification = FormatSpecification(JsonSpecification, RwpmSpecification), + specification = FormatSpecification(Specification.Json, Specification.Rwpm), mediaType = MediaType.DIVINA_MANIFEST, fileExtension = FileExtension("json") ) @@ -316,7 +286,7 @@ class AssetSnifferTest { @Test fun `sniff AVIF`() = runBlocking { val format = Format( - specification = FormatSpecification(AvifSpecification), + specification = FormatSpecification(Specification.Avif), mediaType = MediaType.AVIF, fileExtension = FileExtension("avif") ) @@ -328,7 +298,7 @@ class AssetSnifferTest { @Test fun `sniff GIF`() = runBlocking { val format = Format( - specification = FormatSpecification(GifSpecification), + specification = FormatSpecification(Specification.Gif), mediaType = MediaType.GIF, fileExtension = FileExtension("gif") ) @@ -340,7 +310,7 @@ class AssetSnifferTest { @Test fun `sniff HTML`() = runBlocking { val format = Format( - specification = FormatSpecification(HtmlSpecification), + specification = FormatSpecification(Specification.Html), mediaType = MediaType.HTML, fileExtension = FileExtension("html") ) @@ -369,7 +339,7 @@ class AssetSnifferTest { @Test fun `sniff XHTML`() = runBlocking { val format = Format( - specification = FormatSpecification(XmlSpecification, HtmlSpecification), + specification = FormatSpecification(Specification.Xml, Specification.Html), mediaType = MediaType.XHTML, fileExtension = FileExtension("xhtml") ) @@ -395,7 +365,7 @@ class AssetSnifferTest { @Test fun `sniff JPEG`() = runBlocking { val format = Format( - specification = FormatSpecification(JpegSpecification), + specification = FormatSpecification(Specification.Jpeg), mediaType = MediaType.JPEG, fileExtension = FileExtension("jpg") ) @@ -412,7 +382,7 @@ class AssetSnifferTest { @Test fun `sniff JXL`() = runBlocking { val format = Format( - specification = FormatSpecification(JxlSpecification), + specification = FormatSpecification(Specification.Jxl), mediaType = MediaType.JXL, fileExtension = FileExtension("jxl") ) @@ -424,7 +394,7 @@ class AssetSnifferTest { @Test fun `sniff RAR`() = runBlocking { val format = Format( - specification = FormatSpecification(RarSpecification), + specification = FormatSpecification(Specification.Rar), mediaType = MediaType.RAR, fileExtension = FileExtension("rar") ) @@ -450,7 +420,7 @@ class AssetSnifferTest { @Test fun `sniff OPDS 1 feed`() = runBlocking { val format = Format( - specification = FormatSpecification(XmlSpecification, Opds1CatalogSpecification), + specification = FormatSpecification(Specification.Xml, Specification.Opds1Catalog), mediaType = MediaType.OPDS1, fileExtension = FileExtension("xml") ) @@ -476,7 +446,7 @@ class AssetSnifferTest { @Test fun `sniff OPDS 1 entry`() = runBlocking { val format = Format( - specification = FormatSpecification(XmlSpecification, Opds1EntrySpecification), + specification = FormatSpecification(Specification.Xml, Specification.Opds1Entry), mediaType = MediaType.OPDS1_ENTRY, fileExtension = FileExtension("xml") ) @@ -494,7 +464,7 @@ class AssetSnifferTest { @Test fun `sniff OPDS 2 feed`() = runBlocking { val format = Format( - specification = FormatSpecification(JsonSpecification, Opds2CatalogSpecification), + specification = FormatSpecification(Specification.Json, Specification.Opds2Catalog), mediaType = MediaType.OPDS2, fileExtension = FileExtension("json") ) @@ -512,7 +482,7 @@ class AssetSnifferTest { @Test fun `sniff OPDS 2 publication`() = runBlocking { val format = Format( - specification = FormatSpecification(JsonSpecification, Opds2PublicationSpecification), + specification = FormatSpecification(Specification.Json, Specification.Opds2Publication), mediaType = MediaType.OPDS2_PUBLICATION, fileExtension = FileExtension("json") ) @@ -530,7 +500,10 @@ class AssetSnifferTest { @Test fun `sniff OPDS authentication document`() = runBlocking { val format = Format( - specification = FormatSpecification(JsonSpecification, OpdsAuthenticationSpecification), + specification = FormatSpecification( + Specification.Json, + Specification.OpdsAuthentication + ), mediaType = MediaType.OPDS_AUTHENTICATION, fileExtension = FileExtension("json") ) @@ -553,9 +526,9 @@ class AssetSnifferTest { fun `sniff LCP protected audiobook`() = runBlocking { val format = Format( specification = FormatSpecification( - ZipSpecification, - RpfSpecification, - LcpSpecification + Specification.Zip, + Specification.Rpf, + Specification.Lcp ), mediaType = MediaType.LCP_PROTECTED_AUDIOBOOK, fileExtension = FileExtension("lcpa") @@ -579,9 +552,9 @@ class AssetSnifferTest { fun `sniff LCP protected PDF`() = runBlocking { val format = Format( specification = FormatSpecification( - ZipSpecification, - RpfSpecification, - LcpSpecification + Specification.Zip, + Specification.Rpf, + Specification.Lcp ), mediaType = MediaType.LCP_PROTECTED_PDF, fileExtension = FileExtension("lcpdf") @@ -604,7 +577,7 @@ class AssetSnifferTest { @Test fun `sniff LCP license document`() = runBlocking { val format = Format( - specification = FormatSpecification(JsonSpecification, LcpLicenseSpecification), + specification = FormatSpecification(Specification.Json, Specification.LcpLicense), mediaType = MediaType.LCP_LICENSE_DOCUMENT, fileExtension = FileExtension("lcpl") ) @@ -626,7 +599,7 @@ class AssetSnifferTest { @Test fun `sniff LPF`() = runBlocking { val format = Format( - specification = FormatSpecification(ZipSpecification, LpfSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Lpf), mediaType = MediaType.LPF, fileExtension = FileExtension("lpf") ) @@ -652,7 +625,7 @@ class AssetSnifferTest { @Test fun `sniff PDF`() = runBlocking { val format = Format( - specification = FormatSpecification(PdfSpecification), + specification = FormatSpecification(Specification.Pdf), mediaType = MediaType.PDF, fileExtension = FileExtension("pdf") ) @@ -674,7 +647,7 @@ class AssetSnifferTest { @Test fun `sniff PNG`() = runBlocking { val format = Format( - specification = FormatSpecification(PngSpecification), + specification = FormatSpecification(Specification.Png), mediaType = MediaType.PNG, fileExtension = FileExtension("png") ) @@ -686,7 +659,7 @@ class AssetSnifferTest { @Test fun `sniff TIFF`() = runBlocking { val format = Format( - specification = FormatSpecification(TiffSpecification), + specification = FormatSpecification(Specification.Tiff), mediaType = MediaType.TIFF, fileExtension = FileExtension("tiff") ) @@ -703,7 +676,7 @@ class AssetSnifferTest { @Test fun `sniff WebP`() = runBlocking { val format = Format( - specification = FormatSpecification(WebpSpecification), + specification = FormatSpecification(Specification.Webp), mediaType = MediaType.WEBP, fileExtension = FileExtension("webp") ) @@ -715,7 +688,7 @@ class AssetSnifferTest { @Test fun `sniff WebPub`() = runBlocking { val format = Format( - specification = FormatSpecification(ZipSpecification, RpfSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Rpf), mediaType = MediaType.READIUM_WEBPUB, fileExtension = FileExtension("webpub") ) @@ -738,9 +711,9 @@ class AssetSnifferTest { fun `Sniff LCP protected Readium package`() = runBlocking { val format = Format( specification = FormatSpecification( - ZipSpecification, - RpfSpecification, - LcpSpecification + Specification.Zip, + Specification.Rpf, + Specification.Lcp ), mediaType = MediaType.READIUM_WEBPUB, fileExtension = FileExtension("webpub") @@ -755,7 +728,7 @@ class AssetSnifferTest { @Test fun `sniff WebPub manifest`() = runBlocking { val format = Format( - specification = FormatSpecification(JsonSpecification, RwpmSpecification), + specification = FormatSpecification(Specification.Json, Specification.Rwpm), mediaType = MediaType.READIUM_WEBPUB_MANIFEST, fileExtension = FileExtension("json") ) @@ -773,7 +746,7 @@ class AssetSnifferTest { @Test fun `sniff W3C WPUB manifest`() = runBlocking { val format = Format( - specification = FormatSpecification(JsonSpecification, W3cPubManifestSpecification), + specification = FormatSpecification(Specification.Json, Specification.W3cPubManifest), mediaType = MediaType.W3C_WPUB_MANIFEST, fileExtension = FileExtension("json") ) @@ -787,7 +760,7 @@ class AssetSnifferTest { @Test fun `sniff ZAB`() = runBlocking { val format = Format( - specification = FormatSpecification(ZipSpecification, InformalAudiobookSpecification), + specification = FormatSpecification(Specification.Zip, Specification.InformalAudiobook), mediaType = MediaType.ZAB, fileExtension = FileExtension("zab") ) @@ -805,7 +778,7 @@ class AssetSnifferTest { @Test fun `sniff JSON`() = runBlocking { val format = Format( - specification = FormatSpecification(JsonSpecification), + specification = FormatSpecification(Specification.Json), mediaType = MediaType.JSON, fileExtension = FileExtension("json") ) @@ -819,7 +792,7 @@ class AssetSnifferTest { @Test fun `sniff JSON problem details`() = runBlocking { val format = Format( - specification = FormatSpecification(JsonSpecification, ProblemDetailsSpecification), + specification = FormatSpecification(Specification.Json, Specification.ProblemDetails), mediaType = MediaType.JSON_PROBLEM_DETAILS, fileExtension = FileExtension("json") ) @@ -844,7 +817,7 @@ class AssetSnifferTest { } private val cssFormat = Format( - specification = FormatSpecification(CssSpecification), + specification = FormatSpecification(Specification.Css), mediaType = MediaType.CSS, fileExtension = FileExtension("css") ) @@ -862,7 +835,7 @@ class AssetSnifferTest { } private val jsFormat = Format( - specification = FormatSpecification(JavaScriptSpecification), + specification = FormatSpecification(Specification.JavaScript), mediaType = MediaType.JAVASCRIPT, fileExtension = FileExtension("js") ) diff --git a/readium/shared/src/test/java/org/readium/r2/shared/util/format/DefaultSniffersTest.kt b/readium/shared/src/test/java/org/readium/r2/shared/util/format/DefaultSniffersTest.kt index 311f969596..b771a174df 100644 --- a/readium/shared/src/test/java/org/readium/r2/shared/util/format/DefaultSniffersTest.kt +++ b/readium/shared/src/test/java/org/readium/r2/shared/util/format/DefaultSniffersTest.kt @@ -20,7 +20,7 @@ import org.robolectric.RobolectricTestRunner class DefaultSniffersTest { private val epubFormat = Format( - specification = FormatSpecification(ZipSpecification, EpubSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Epub), mediaType = MediaType.EPUB, fileExtension = FileExtension("epub") ) @@ -41,7 +41,7 @@ class DefaultSniffersTest { @Test fun `Sniff Adobe ADEPT`() = runBlocking { assertEquals( - epubFormat.copy(specification = epubFormat.specification + AdeptSpecification), + epubFormat.copy(specification = epubFormat.specification + Specification.Adept), EpubDrmSniffer.sniffContainer( format = epubFormat, container = TestContainer( @@ -64,7 +64,7 @@ class DefaultSniffersTest { @Test fun `Sniff Adobe ADEPT from rights xml`() = runBlocking { assertEquals( - epubFormat.copy(specification = epubFormat.specification + AdeptSpecification), + epubFormat.copy(specification = epubFormat.specification + Specification.Adept), EpubDrmSniffer.sniffContainer( format = epubFormat, container = TestContainer( @@ -78,7 +78,7 @@ class DefaultSniffersTest { @Test fun `Sniff LCP protected EPUB`() = runBlocking { assertEquals( - epubFormat.copy(specification = epubFormat.specification + LcpSpecification), + epubFormat.copy(specification = epubFormat.specification + Specification.Lcp), EpubDrmSniffer.sniffContainer( format = epubFormat, container = TestContainer(Url("META-INF/license.lcpl")!! to "{}") @@ -89,7 +89,7 @@ class DefaultSniffersTest { @Test fun `Sniff LCP protected EPUB missing the license`() = runBlocking { assertEquals( - epubFormat.copy(specification = epubFormat.specification + LcpSpecification), + epubFormat.copy(specification = epubFormat.specification + Specification.Lcp), EpubDrmSniffer.sniffContainer( format = epubFormat, container = TestContainer( diff --git a/readium/shared/src/test/java/org/readium/r2/shared/util/resource/ZipContainerTest.kt b/readium/shared/src/test/java/org/readium/r2/shared/util/resource/ZipContainerTest.kt index 6f92ea2b71..eacfee67c5 100644 --- a/readium/shared/src/test/java/org/readium/r2/shared/util/resource/ZipContainerTest.kt +++ b/readium/shared/src/test/java/org/readium/r2/shared/util/resource/ZipContainerTest.kt @@ -23,10 +23,9 @@ import org.readium.r2.shared.util.Url import org.readium.r2.shared.util.checkSuccess import org.readium.r2.shared.util.data.Container import org.readium.r2.shared.util.file.DirectoryContainer -import org.readium.r2.shared.util.format.EpubSpecification import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.FormatSpecification -import org.readium.r2.shared.util.format.ZipSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.mediatype.MediaType import org.readium.r2.shared.util.use import org.readium.r2.shared.util.zip.FileZipArchiveProvider @@ -44,7 +43,7 @@ class ZipContainerTest(val sut: suspend () -> Container) { val epubZip = ZipContainerTest::class.java.getResource("epub.epub") assertNotNull(epubZip) val format = Format( - specification = FormatSpecification(ZipSpecification, EpubSpecification), + specification = FormatSpecification(Specification.Zip, Specification.Epub), mediaType = MediaType.EPUB, fileExtension = FileExtension("epub") ) diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/audio/AudioParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/audio/AudioParser.kt index d8563479b6..59d16e642a 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/audio/AudioParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/audio/AudioParser.kt @@ -20,18 +20,8 @@ import org.readium.r2.shared.util.asset.ContainerAsset import org.readium.r2.shared.util.asset.ResourceAsset import org.readium.r2.shared.util.data.Container import org.readium.r2.shared.util.data.ReadError -import org.readium.r2.shared.util.format.AacSpecification -import org.readium.r2.shared.util.format.AiffSpecification -import org.readium.r2.shared.util.format.FlacSpecification import org.readium.r2.shared.util.format.Format -import org.readium.r2.shared.util.format.InformalAudiobookSpecification -import org.readium.r2.shared.util.format.Mp3Specification -import org.readium.r2.shared.util.format.Mp4Specification -import org.readium.r2.shared.util.format.OggSpecification -import org.readium.r2.shared.util.format.OpusSpecification import org.readium.r2.shared.util.format.Specification -import org.readium.r2.shared.util.format.WavSpecification -import org.readium.r2.shared.util.format.WebmSpecification import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.logging.WarningLogger import org.readium.r2.shared.util.resource.Resource @@ -79,7 +69,7 @@ public class AudioParser( private suspend fun parseContainerAsset( asset: ContainerAsset ): Try { - if (!asset.format.conformsTo(InformalAudiobookSpecification)) { + if (!asset.format.conformsTo(Specification.InformalAudiobook)) { return Try.failure(PublicationParser.ParseError.FormatNotSupported()) } @@ -141,14 +131,14 @@ public class AudioParser( private val audioSpecifications: Set = setOf( - AacSpecification, - AiffSpecification, - FlacSpecification, - Mp4Specification, - Mp3Specification, - OggSpecification, - OpusSpecification, - WavSpecification, - WebmSpecification + Specification.Aac, + Specification.Aiff, + Specification.Flac, + Specification.Mp4, + Specification.Mp3, + Specification.Ogg, + Specification.Opus, + Specification.Wav, + Specification.Webm ) } diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/EpubParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/EpubParser.kt index 3129dd9617..7dcd219a8c 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/EpubParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/epub/EpubParser.kt @@ -26,7 +26,7 @@ import org.readium.r2.shared.util.data.Readable import org.readium.r2.shared.util.data.decodeXml import org.readium.r2.shared.util.data.readDecodeOrElse import org.readium.r2.shared.util.data.readDecodeOrNull -import org.readium.r2.shared.util.format.EpubSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.fromEpubHref import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.logging.WarningLogger @@ -52,7 +52,7 @@ public class EpubParser( asset: Asset, warnings: WarningLogger? ): Try { - if (asset !is ContainerAsset || !asset.format.conformsTo(EpubSpecification)) { + if (asset !is ContainerAsset || !asset.format.conformsTo(Specification.Epub)) { return Try.failure(PublicationParser.ParseError.FormatNotSupported()) } diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/image/ImageParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/image/ImageParser.kt index 9a31fcc2b7..a65efef755 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/image/ImageParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/image/ImageParser.kt @@ -21,17 +21,8 @@ import org.readium.r2.shared.util.asset.ContainerAsset import org.readium.r2.shared.util.asset.ResourceAsset import org.readium.r2.shared.util.data.Container import org.readium.r2.shared.util.data.ReadError -import org.readium.r2.shared.util.format.AvifSpecification -import org.readium.r2.shared.util.format.BmpSpecification import org.readium.r2.shared.util.format.Format -import org.readium.r2.shared.util.format.GifSpecification -import org.readium.r2.shared.util.format.InformalComicSpecification -import org.readium.r2.shared.util.format.JpegSpecification -import org.readium.r2.shared.util.format.JxlSpecification -import org.readium.r2.shared.util.format.PngSpecification import org.readium.r2.shared.util.format.Specification -import org.readium.r2.shared.util.format.TiffSpecification -import org.readium.r2.shared.util.format.WebpSpecification import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.logging.WarningLogger import org.readium.r2.shared.util.mediatype.MediaType @@ -80,7 +71,7 @@ public class ImageParser( private suspend fun parseContainerAsset( asset: ContainerAsset ): Try { - if (!asset.format.conformsTo(InformalComicSpecification)) { + if (!asset.format.conformsTo(Specification.InformalComic)) { return Try.failure(PublicationParser.ParseError.FormatNotSupported()) } @@ -147,13 +138,13 @@ public class ImageParser( private val bitmapSpecifications: Set = setOf( - AvifSpecification, - BmpSpecification, - GifSpecification, - JpegSpecification, - JxlSpecification, - PngSpecification, - TiffSpecification, - WebpSpecification + Specification.Avif, + Specification.Bmp, + Specification.Gif, + Specification.Jpeg, + Specification.Jxl, + Specification.Png, + Specification.Tiff, + Specification.Webp ) } diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/pdf/PdfParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/pdf/PdfParser.kt index 1a4382dc2c..f9d8910964 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/pdf/PdfParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/pdf/PdfParser.kt @@ -14,7 +14,7 @@ import org.readium.r2.shared.publication.services.InMemoryCoverService import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.asset.Asset import org.readium.r2.shared.util.asset.ResourceAsset -import org.readium.r2.shared.util.format.PdfSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.logging.WarningLogger import org.readium.r2.shared.util.mediatype.MediaType @@ -38,7 +38,7 @@ public class PdfParser( asset: Asset, warnings: WarningLogger? ): Try { - if (asset !is ResourceAsset || !asset.format.conformsTo(PdfSpecification)) { + if (asset !is ResourceAsset || !asset.format.conformsTo(Specification.Pdf)) { return Try.failure(PublicationParser.ParseError.FormatNotSupported()) } diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt index eb2c7d1541..809593f2a6 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/readium/ReadiumWebPubParser.kt @@ -7,6 +7,7 @@ package org.readium.r2.streamer.parser.readium import android.content.Context +import org.readium.r2.shared.publication.Manifest import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.publication.services.InMemoryCacheService import org.readium.r2.shared.publication.services.PerResourcePositionsService @@ -27,9 +28,7 @@ import org.readium.r2.shared.util.data.ReadError import org.readium.r2.shared.util.data.decodeRwpm import org.readium.r2.shared.util.data.readDecodeOrElse import org.readium.r2.shared.util.format.FormatSpecification -import org.readium.r2.shared.util.format.LcpSpecification -import org.readium.r2.shared.util.format.RpfSpecification -import org.readium.r2.shared.util.format.RwpmSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.http.HttpClient import org.readium.r2.shared.util.http.HttpContainer import org.readium.r2.shared.util.logging.WarningLogger @@ -62,7 +61,7 @@ public class ReadiumWebPubParser( container: Container, formatSpecification: FormatSpecification ): Try { - if (!formatSpecification.conformsTo(RpfSpecification)) { + if (!formatSpecification.conformsTo(Specification.Rpf)) { return Try.failure(PublicationParser.ParseError.FormatNotSupported()) } @@ -81,27 +80,15 @@ public class ReadiumWebPubParser( recover = { return Try.failure(PublicationParser.ParseError.Reading(it)) } ) - // Checks the requirements from the LCPDF specification. - // https://readium.org/lcp-specs/notes/lcp-for-pdf.html - val readingOrder = manifest.readingOrder - if (manifest.conformsTo(Publication.Profile.PDF) && formatSpecification.conformsTo( - LcpSpecification - ) && - (readingOrder.isEmpty() || !readingOrder.all { MediaType.PDF.matches(it.mediaType) }) - ) { - return Try.failure( - PublicationParser.ParseError.Reading( - ReadError.Decoding("Invalid LCP Protected PDF.") - ) - ) - } + checkProfileRequirements(manifest) + ?.let { return Try.failure(it) } val servicesBuilder = Publication.ServicesBuilder().apply { cacheServiceFactory = InMemoryCacheService.createFactory(context) positionsServiceFactory = when { manifest.conformsTo(Publication.Profile.PDF) && formatSpecification.conformsTo( - LcpSpecification + Specification.Lcp ) -> pdfFactory?.let { LcpdfPositionsService.create(it) } manifest.conformsTo(Publication.Profile.DIVINA) -> @@ -122,11 +109,42 @@ public class ReadiumWebPubParser( return Try.success(publicationBuilder) } + private fun checkProfileRequirements(manifest: Manifest): PublicationParser.ParseError? = + when { + manifest.conformsTo(Publication.Profile.PDF) -> { + if (manifest.readingOrder.isEmpty() || + manifest.readingOrder.any { !MediaType.PDF.matches(it.mediaType) } + ) { + PublicationParser.ParseError.Reading( + ReadError.Decoding( + "Publication does not conform to the PDF profile specification." + ) + ) + } else { + null + } + } + manifest.conformsTo(Publication.Profile.AUDIOBOOK) -> { + if (manifest.readingOrder.isEmpty()) { + PublicationParser.ParseError.Reading( + ReadError.Decoding( + "Publication does not conform to the Audiobook profile specification." + ) + ) + } else { + null + } + } + else -> { + null + } + } + private suspend fun parseResourceAsset( resource: Resource, formatSpecification: FormatSpecification ): Try { - if (!formatSpecification.conformsTo(RwpmSpecification)) { + if (!formatSpecification.conformsTo(Specification.Rwpm)) { return Try.failure(PublicationParser.ParseError.FormatNotSupported()) } @@ -169,6 +187,6 @@ public class ReadiumWebPubParser( HttpContainer(baseUrl, resources, httpClient) ) - return parseContainerAsset(container, FormatSpecification(RpfSpecification)) + return parseContainerAsset(container, FormatSpecification(Specification.Rpf)) } } diff --git a/readium/streamer/src/test/java/org/readium/r2/streamer/parser/image/ImageParserTest.kt b/readium/streamer/src/test/java/org/readium/r2/streamer/parser/image/ImageParserTest.kt index 6fee0b8388..74419c5fd5 100644 --- a/readium/streamer/src/test/java/org/readium/r2/streamer/parser/image/ImageParserTest.kt +++ b/readium/streamer/src/test/java/org/readium/r2/streamer/parser/image/ImageParserTest.kt @@ -27,9 +27,7 @@ import org.readium.r2.shared.util.checkSuccess import org.readium.r2.shared.util.file.FileResource import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.FormatSpecification -import org.readium.r2.shared.util.format.InformalComicSpecification -import org.readium.r2.shared.util.format.JpegSpecification -import org.readium.r2.shared.util.format.ZipSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.http.DefaultHttpClient import org.readium.r2.shared.util.mediatype.MediaType import org.readium.r2.shared.util.zip.ZipArchiveOpener @@ -52,7 +50,7 @@ class ImageParserTest { val file = fileForResource("futuristic_tales.cbz") val resource = FileResource(file) val format = Format( - specification = FormatSpecification(ZipSpecification, InformalComicSpecification), + specification = FormatSpecification(Specification.Zip, Specification.InformalComic), mediaType = MediaType.CBZ, fileExtension = FileExtension("cbz") ) @@ -64,7 +62,7 @@ class ImageParserTest { val file = fileForResource("futuristic_tales.jpg") val resource = FileResource(file) val format = Format( - specification = FormatSpecification(JpegSpecification), + specification = FormatSpecification(Specification.Jpeg), mediaType = MediaType.JPEG, fileExtension = FileExtension("jpg") ) diff --git a/test-app/src/main/java/org/readium/r2/testapp/domain/PublicationRetriever.kt b/test-app/src/main/java/org/readium/r2/testapp/domain/PublicationRetriever.kt index a0b2124f5c..f92f973db4 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/domain/PublicationRetriever.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/domain/PublicationRetriever.kt @@ -22,7 +22,7 @@ import org.readium.r2.shared.util.data.ReadError import org.readium.r2.shared.util.file.FileSystemError import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.FormatHints -import org.readium.r2.shared.util.format.LcpLicenseSpecification +import org.readium.r2.shared.util.format.Specification import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.http.HttpClient import org.readium.r2.shared.util.http.HttpRequest @@ -196,7 +196,7 @@ private class LocalPublicationRetriever( if ( sourceAsset is ResourceAsset && - sourceAsset.format.conformsTo(LcpLicenseSpecification) + sourceAsset.format.conformsTo(Specification.LcpLicense) ) { return if (lcpService == null) { sourceAsset.close()