Skip to content

Commit

Permalink
Revamp format (readium#428)
Browse files Browse the repository at this point in the history
  • Loading branch information
qnga committed Jan 8, 2024
1 parent bfadfa8 commit b247622
Show file tree
Hide file tree
Showing 41 changed files with 2,035 additions and 1,687 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ 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.Format
import org.readium.r2.shared.util.format.Trait
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.getOrElse
import org.readium.r2.shared.util.resource.Resource
import org.readium.r2.shared.util.resource.TransformingContainer
Expand All @@ -44,25 +45,23 @@ internal class LcpContentProtection(
asset: Asset,
credentials: String?,
allowUserInteraction: Boolean
): Try<ContentProtection.OpenResult, ContentProtection.OpenError> {
if (
!asset.format.conformsTo(Trait.LCP_PROTECTED) &&
!asset.format.conformsTo(Format.LCP_LICENSE_DOCUMENT)
) {
return Try.failure(ContentProtection.OpenError.AssetNotSupported())
}

return when (asset) {
): Try<ContentProtection.OpenResult, ContentProtection.OpenError> =
when (asset) {
is ContainerAsset -> openPublication(asset, credentials, allowUserInteraction)
is ResourceAsset -> openLicense(asset, credentials, allowUserInteraction)
}
}

private suspend fun openPublication(
asset: ContainerAsset,
credentials: String?,
allowUserInteraction: Boolean
): Try<ContentProtection.OpenResult, ContentProtection.OpenError> {
if (
!asset.format.conformsTo(LcpSpecification)
) {
return Try.failure(ContentProtection.OpenError.AssetNotSupported())
}

val license = retrieveLicense(asset, credentials, allowUserInteraction)
return createResultAsset(asset, license)
}
Expand All @@ -88,7 +87,9 @@ internal class LcpContentProtection(

val encryptionData =
when {
asset.format.conformsTo(Trait.EPUB) -> parseEncryptionDataEpub(asset.container)
asset.format.conformsTo(EpubSpecification) -> parseEncryptionDataEpub(
asset.container
)
else -> parseEncryptionDataRpf(asset.container)
}
.getOrElse { return Try.failure(ContentProtection.OpenError.Reading(it)) }
Expand Down Expand Up @@ -146,6 +147,10 @@ internal class LcpContentProtection(
credentials: String?,
allowUserInteraction: Boolean
): Try<ContentProtection.OpenResult, ContentProtection.OpenError> {
if (!licenseAsset.format.conformsTo(LcpLicenseSpecification)) {
return Try.failure(ContentProtection.OpenError.AssetNotSupported())
}

val license = retrieveLicense(licenseAsset, credentials, allowUserInteraction)

val licenseDoc = license.getOrNull()?.license
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ import org.readium.r2.lcp.license.model.LicenseDocument
import org.readium.r2.shared.extensions.tryOrLog
import org.readium.r2.shared.util.AbsoluteUrl
import org.readium.r2.shared.util.ErrorException
import org.readium.r2.shared.util.FileExtension
import org.readium.r2.shared.util.asset.AssetSniffer
import org.readium.r2.shared.util.downloads.DownloadManager
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.FormatRegistry
import org.readium.r2.shared.util.format.Trait
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.getOrElse
import org.readium.r2.shared.util.mediatype.MediaType

/**
* Utility to acquire a protected publication from an LCP License Document.
Expand Down Expand Up @@ -128,9 +132,6 @@ public class LcpPublicationRetriever(
private val coroutineScope: CoroutineScope =
MainScope()

private val formatRegistry: FormatRegistry =
FormatRegistry()

private val downloadsRepository: LcpDownloadsRepository =
LcpDownloadsRepository(context)

Expand Down Expand Up @@ -195,7 +196,7 @@ public class LcpPublicationRetriever(
}
downloadsRepository.removeDownload(requestId.value)

val baseFormat =
val format =
assetSniffer.sniff(
download.file,
FormatHints(
Expand All @@ -204,13 +205,21 @@ public class LcpPublicationRetriever(
download.mediaType
)
)
).getOrElse { Format.EPUB }

val format = baseFormat + Trait.LCP_PROTECTED
).getOrElse {
Format(
specification = FormatSpecification(
ZipSpecification,
EpubSpecification,
LcpSpecification
),
mediaType = MediaType.EPUB,
fileExtension = FileExtension("epub")
)
}

try {
// Saves the License Document into the downloaded publication
val container = createLicenseContainer(download.file, format)
val container = createLicenseContainer(download.file, format.specification)
container.write(license)
} catch (e: Exception) {
tryOrLog { download.file.delete() }
Expand Down Expand Up @@ -279,7 +288,4 @@ public class LcpPublicationRetriever(
listeners.remove(lcpRequestId)
}
}

private val Format.fileExtension: String get() =
formatRegistry[this]?.fileExtension?.value ?: "epub"
}
29 changes: 19 additions & 10 deletions readium/lcp/src/main/java/org/readium/r2/lcp/LcpService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import org.readium.r2.shared.publication.protection.ContentProtection
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.asset.AssetSniffer
import org.readium.r2.shared.util.downloads.DownloadManager
import org.readium.r2.shared.util.format.Format

Expand All @@ -43,10 +42,12 @@ public interface LcpService {
* Returns if the file is a LCP license document or a publication protected by LCP.
*/
@Deprecated(
"Use an AssetSniffer and check the returned format for Trait.LCP_PROTECTED",
"Use an AssetSniffer and check the conformance of the returned format to LcpSpecification",
level = DeprecationLevel.ERROR
)
public suspend fun isLcpProtected(file: File): Boolean
public suspend fun isLcpProtected(file: File): Boolean {
throw NotImplementedError()
}

/**
* Acquires a protected publication from a standalone LCPL's bytes.
Expand All @@ -60,7 +61,9 @@ public interface LcpService {
ReplaceWith("publicationRetriever()"),
level = DeprecationLevel.ERROR
)
public suspend fun acquirePublication(lcpl: ByteArray, onProgress: (Double) -> Unit = {}): Try<AcquiredPublication, LcpError>
public suspend fun acquirePublication(lcpl: ByteArray, onProgress: (Double) -> Unit = {}): Try<AcquiredPublication, LcpError> {
throw NotImplementedError()
}

/**
* Acquires a protected publication from a standalone LCPL file.
Expand Down Expand Up @@ -88,13 +91,21 @@ public interface LcpService {
* The request will be cancelled if no passphrase is found in the LCP passphrase storage
* and the provided [authentication].
* @param allowUserInteraction Indicates whether the user can be prompted for their passphrase.
* @param sender Free object that can be used by reading apps to give some UX context when
* presenting dialogs with [LcpAuthenticating].
*/
@Deprecated(
"Use the overload taking an asset instead.",
level = DeprecationLevel.ERROR
)
public suspend fun retrieveLicense(
file: File,
format: Format,
authentication: LcpAuthenticating,
allowUserInteraction: Boolean
): Try<LcpLicense, LcpError>
authentication: LcpAuthenticating = LcpDialogAuthentication(),
allowUserInteraction: Boolean,
sender: Any? = null
): Try<LcpLicense, LcpError>? {
throw NotImplementedError()
}

/**
* Opens the LCP license of a protected publication, to access its DRM metadata and decipher
Expand Down Expand Up @@ -164,7 +175,6 @@ public interface LcpService {
public operator fun invoke(
context: Context,
assetOpener: AssetOpener,
assetSniffer: AssetSniffer,
downloadManager: DownloadManager
): LcpService? {
if (!LcpClient.isAvailable()) {
Expand All @@ -191,7 +201,6 @@ public interface LcpService {
passphrases = passphrases,
context = context,
assetOpener = assetOpener,
assetSniffer = assetSniffer,
downloadManager = downloadManager
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ 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.Format
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.resource.Resource

private val LICENSE_IN_EPUB = Url("META-INF/license.lcpl")!!
Expand All @@ -39,11 +41,14 @@ internal interface WritableLicenseContainer : LicenseContainer {

internal fun createLicenseContainer(
file: File,
format: Format
formatSpecification: FormatSpecification
): WritableLicenseContainer =
when {
format.conformsTo(Format.EPUB) -> FileZipLicenseContainer(file.path, LICENSE_IN_EPUB)
format.conformsTo(Format.LCP_LICENSE_DOCUMENT) -> LcplLicenseContainer(file)
formatSpecification.conformsTo(EpubSpecification) -> FileZipLicenseContainer(
file.path,
LICENSE_IN_EPUB
)
formatSpecification.conformsTo(LcpLicenseSpecification) -> LcplLicenseContainer(file)
// Assuming it's a Readium WebPub package (e.g. audiobook, LCPDF, etc.) as a fallback
else -> FileZipLicenseContainer(file.path, LICENSE_IN_RPF)
}
Expand All @@ -53,15 +58,19 @@ internal fun createLicenseContainer(
asset: Asset
): LicenseContainer =
when (asset) {
is ResourceAsset -> createLicenseContainer(asset.resource, asset.format)
is ContainerAsset -> createLicenseContainer(context, asset.container, asset.format)
is ResourceAsset -> createLicenseContainer(asset.resource, asset.format.specification)
is ContainerAsset -> createLicenseContainer(
context,
asset.container,
asset.format.specification
)
}

internal fun createLicenseContainer(
resource: Resource,
format: Format
formatSpecification: FormatSpecification
): LicenseContainer {
if (!format.conformsTo(Format.LCP_LICENSE_DOCUMENT)) {
if (!formatSpecification.conformsTo(LcpLicenseSpecification)) {
throw LcpException(LcpError.Container.OpenFailed)
}

Expand All @@ -76,10 +85,10 @@ internal fun createLicenseContainer(
internal fun createLicenseContainer(
context: Context,
container: Container<Resource>,
format: Format
formatSpecification: FormatSpecification
): LicenseContainer {
val licensePath = when {
format.conformsTo(Format.EPUB) -> LICENSE_IN_EPUB
formatSpecification.conformsTo(EpubSpecification) -> LICENSE_IN_EPUB
// Assuming it's a Readium WebPub package (e.g. audiobook, LCPDF, etc.) as a fallback
else -> LICENSE_IN_RPF
}
Expand Down
Loading

0 comments on commit b247622

Please sign in to comment.