diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ff5ab977..2225c6ad2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file. Take a look * Added an initializer parameter for providing a custom device identifier (contributed by [@dewantawsif](https://github.com/readium/swift-toolkit/pull/661)). * You must ensure the identifier is unique and stable for the device (persist and reuse across app launches). * Recommended: generate an app-scoped UUID and store it securely (e.g., in the Keychain); avoid hardware or advertising identifiers. +* You can use `LCPService.injectLicenseDocument(_:in)` to insert an LCPL into a package, if you downloaded it manually instead of using `LCPService.acquirePublication()`. ### Changed diff --git a/Sources/LCP/LCPService.swift b/Sources/LCP/LCPService.swift index a61506e4c..947edf67a 100644 --- a/Sources/LCP/LCPService.swift +++ b/Sources/LCP/LCPService.swift @@ -80,6 +80,18 @@ public final class LCPService: Loggable { } } + /// Injects a `licenseDocument` into a publication package at `url`. + /// + /// This is useful if you downloaded the publication yourself instead of using `acquirePublication`. + public func injectLicenseDocument( + _ license: LicenseDocument, + in url: FileURL + ) async -> Result { + await wrap { + try await licenses.injectLicenseDocument(license, in: url) + } + } + /// Opens the LCP license of a protected publication, to access its DRM /// metadata and decipher its content. /// diff --git a/Sources/LCP/Services/LicensesService.swift b/Sources/LCP/Services/LicensesService.swift index 3e22fe072..437f9d78e 100644 --- a/Sources/LCP/Services/LicensesService.swift +++ b/Sources/LCP/Services/LicensesService.swift @@ -120,26 +120,47 @@ final class LicensesService: Loggable { onProgress: { onProgress(.percent(Float($0))) } ).get() - var hints = FormatHints() - if let type = license.link(for: .publication)?.mediaType { - hints.mediaTypes.append(type) + let format = try await injectLicenseAndGetFormat( + license, + in: download.location, + mediaTypeHint: download.mediaType + ) + + return LCPAcquiredPublication( + localURL: download.location, + format: format, + suggestedFilename: format.fileExtension.appendedToFilename(license.id), + licenseDocument: license + ) + } + + func injectLicenseDocument( + _ license: LicenseDocument, + in url: FileURL + ) async throws { + let _ = try await injectLicenseAndGetFormat(license, in: url, mediaTypeHint: nil) + } + + private func injectLicenseAndGetFormat( + _ license: LicenseDocument, + in url: FileURL, + mediaTypeHint: MediaType? = nil + ) async throws -> Format { + var formatHints = FormatHints() + if let type = license.publicationLink.mediaType { + formatHints.mediaTypes.append(type) } - if let type = download.mediaType { - hints.mediaTypes.append(type) + if let type = mediaTypeHint { + formatHints.mediaTypes.append(type) } - let asset = try await assetRetriever.retrieve(url: download.location, hints: hints) + let asset = try await assetRetriever.retrieve(url: url, hints: formatHints) .mapError { LCPError.licenseContainer(ContainerError.openFailed($0)) } .get() try await injectLicense(license, in: asset) - return LCPAcquiredPublication( - localURL: download.location, - format: asset.format, - suggestedFilename: asset.format.fileExtension.appendedToFilename(license.id), - licenseDocument: license - ) + return asset.format } private func readLicense(from lcpl: LicenseDocumentSource) async throws -> LicenseDocument? {