From ef2314cd0e3cdb8ed99b42f88c4eb4f35ddd15d5 Mon Sep 17 00:00:00 2001 From: Quentin Gliosca Date: Wed, 29 Oct 2025 16:43:21 +0100 Subject: [PATCH 1/3] Add injectLicense to LCPService --- Sources/LCP/LCPService.swift | 12 +++++ Sources/LCP/Services/LicensesService.swift | 51 +++++++++++++++------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/Sources/LCP/LCPService.swift b/Sources/LCP/LCPService.swift index a61506e4c..7690ece64 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 injectLicense( + _ license: LicenseDocument, + in url : FileURL + ) async -> Result { + await wrap { + try await licenses.injectLicense(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..a707a98b8 100644 --- a/Sources/LCP/Services/LicensesService.swift +++ b/Sources/LCP/Services/LicensesService.swift @@ -120,28 +120,49 @@ 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) - } - if let type = download.mediaType { - hints.mediaTypes.append(type) - } - - let asset = try await assetRetriever.retrieve(url: download.location, hints: hints) - .mapError { LCPError.licenseContainer(ContainerError.openFailed($0)) } - .get() - - try await injectLicense(license, in: asset) + let format = try await injectLicenseAndGetFormat( + license, + in: download.location, + mediaTypeHint: download.mediaType + ) return LCPAcquiredPublication( localURL: download.location, - format: asset.format, - suggestedFilename: asset.format.fileExtension.appendedToFilename(license.id), + format: format, + suggestedFilename: format.fileExtension.appendedToFilename(license.id), licenseDocument: license ) } + func injectLicense( + _ 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 = mediaTypeHint { + formatHints.mediaTypes.append(type) + } + + let asset = try await assetRetriever.retrieve(url: url, hints: formatHints) + .mapError { LCPError.licenseContainer(ContainerError.openFailed($0)) } + .get() + + try await injectLicense(license, in: asset) + + return asset.format + } + private func readLicense(from lcpl: LicenseDocumentSource) async throws -> LicenseDocument? { switch lcpl { case let .data(data): From fe9cc88d29fada3f54c1122a245fd2863acc39f8 Mon Sep 17 00:00:00 2001 From: Quentin Gliosca Date: Wed, 29 Oct 2025 16:48:12 +0100 Subject: [PATCH 2/3] Format --- Sources/LCP/LCPService.swift | 2 +- Sources/LCP/Services/LicensesService.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/LCP/LCPService.swift b/Sources/LCP/LCPService.swift index 7690ece64..0eb8d22e5 100644 --- a/Sources/LCP/LCPService.swift +++ b/Sources/LCP/LCPService.swift @@ -85,7 +85,7 @@ public final class LCPService: Loggable { /// This is useful if you downloaded the publication yourself instead of using `acquirePublication`. public func injectLicense( _ license: LicenseDocument, - in url : FileURL + in url: FileURL ) async -> Result { await wrap { try await licenses.injectLicense(license, in: url) diff --git a/Sources/LCP/Services/LicensesService.swift b/Sources/LCP/Services/LicensesService.swift index a707a98b8..55167b117 100644 --- a/Sources/LCP/Services/LicensesService.swift +++ b/Sources/LCP/Services/LicensesService.swift @@ -153,13 +153,13 @@ final class LicensesService: Loggable { if let type = mediaTypeHint { formatHints.mediaTypes.append(type) } - + let asset = try await assetRetriever.retrieve(url: url, hints: formatHints) .mapError { LCPError.licenseContainer(ContainerError.openFailed($0)) } .get() - + try await injectLicense(license, in: asset) - + return asset.format } From 33ac976d579f7dbc85789876dfcb2619004638de Mon Sep 17 00:00:00 2001 From: Quentin Gliosca Date: Wed, 29 Oct 2025 17:39:44 +0100 Subject: [PATCH 3/3] Modify method name and CHANGELOG --- CHANGELOG.md | 1 + Sources/LCP/LCPService.swift | 4 ++-- Sources/LCP/Services/LicensesService.swift | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) 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 0eb8d22e5..947edf67a 100644 --- a/Sources/LCP/LCPService.swift +++ b/Sources/LCP/LCPService.swift @@ -83,12 +83,12 @@ 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 injectLicense( + public func injectLicenseDocument( _ license: LicenseDocument, in url: FileURL ) async -> Result { await wrap { - try await licenses.injectLicense(license, in: url) + try await licenses.injectLicenseDocument(license, in: url) } } diff --git a/Sources/LCP/Services/LicensesService.swift b/Sources/LCP/Services/LicensesService.swift index 55167b117..437f9d78e 100644 --- a/Sources/LCP/Services/LicensesService.swift +++ b/Sources/LCP/Services/LicensesService.swift @@ -134,7 +134,7 @@ final class LicensesService: Loggable { ) } - func injectLicense( + func injectLicenseDocument( _ license: LicenseDocument, in url: FileURL ) async throws {