Skip to content

Commit

Permalink
Move downloadManagerProvider to the constructor level in LcpService
Browse files Browse the repository at this point in the history
  • Loading branch information
qnga committed Sep 2, 2023
1 parent 5e15894 commit fd9af69
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 22 deletions.
42 changes: 25 additions & 17 deletions readium/lcp/src/main/java/org/readium/r2/lcp/LcpService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import android.content.Context
import java.io.File
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.readium.r2.lcp.auth.LcpDialogAuthentication
import org.readium.r2.lcp.license.model.LicenseDocument
Expand Down Expand Up @@ -55,9 +53,18 @@ public interface LcpService {
* Acquires a protected publication from a standalone LCPL's bytes.
*
* You can cancel the on-going acquisition by cancelling its parent coroutine context.
*
* @Deprecated(
"Use a LcpPublicationRetriever instead.",
ReplaceWith("publicationRetriever()"),
level = DeprecationLevel.ERROR
)
* @param onProgress Callback to follow the acquisition progress from 0.0 to 1.0.
*/
@Deprecated(
"Use a LcpPublicationRetriever instead.",
ReplaceWith("publicationRetriever()"),
level = DeprecationLevel.ERROR
)
public suspend fun acquirePublication(lcpl: ByteArray, onProgress: (Double) -> Unit = {}): Try<AcquiredPublication, LcpException>

/**
Expand All @@ -67,14 +74,15 @@ public interface LcpService {
*
* @param onProgress Callback to follow the acquisition progress from 0.0 to 1.0.
*/
@Deprecated(
"Use a LcpPublicationRetriever instead.",
ReplaceWith("publicationRetriever()"),
level = DeprecationLevel.ERROR
)
public suspend fun acquirePublication(lcpl: File, onProgress: (Double) -> Unit = {}): Try<AcquiredPublication, LcpException> = withContext(
Dispatchers.IO
) {
try {
acquirePublication(lcpl.readBytes(), onProgress)
} catch (e: Exception) {
Try.failure(LcpException.wrap(e))
}
throw NotImplementedError()
}

/**
Expand Down Expand Up @@ -120,10 +128,12 @@ public interface LcpService {
* Creates a [LcpPublicationRetriever] instance which can be used to acquire a protected
* publication from standalone LCPL's bytes.
*
* @param listener listener to implement to be notified about the status of the download.
* You should use only one instance of [LcpPublicationRetriever] in your app. If you don't,
* behaviour is undefined.
*
* @param listener listener to implement to be notified about the status of the downloads.
*/
public fun publicationRetriever(
downloadManagerProvider: DownloadManagerProvider,
listener: LcpPublicationRetriever.Listener
): LcpPublicationRetriever

Expand Down Expand Up @@ -169,7 +179,8 @@ public interface LcpService {
public operator fun invoke(
context: Context,
assetRetriever: AssetRetriever,
mediaTypeRetriever: MediaTypeRetriever
mediaTypeRetriever: MediaTypeRetriever,
downloadManagerProvider: DownloadManagerProvider
): LcpService? {
if (!LcpClient.isAvailable()) {
return null
Expand All @@ -195,7 +206,8 @@ public interface LcpService {
passphrases = passphrases,
context = context,
assetRetriever = assetRetriever,
mediaTypeRetriever = mediaTypeRetriever
mediaTypeRetriever = mediaTypeRetriever,
downloadManagerProvider = downloadManagerProvider
)
}

Expand All @@ -219,11 +231,7 @@ public interface LcpService {
authentication: LcpAuthenticating?,
completion: (AcquiredPublication?, LcpException?) -> Unit
) {
GlobalScope.launch {
acquirePublication(lcpl)
.onSuccess { completion(it, null) }
.onFailure { completion(null, it) }
}
throw NotImplementedError()
}

@Deprecated(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ internal class LicensesService(
private val passphrases: PassphrasesService,
private val context: Context,
private val assetRetriever: AssetRetriever,
private val mediaTypeRetriever: MediaTypeRetriever
private val mediaTypeRetriever: MediaTypeRetriever,
private val downloadManagerProvider: DownloadManagerProvider
) : LcpService, CoroutineScope by MainScope() {

override suspend fun isLcpProtected(file: File): Boolean {
Expand All @@ -75,7 +76,6 @@ internal class LicensesService(
LcpContentProtection(this, authentication, assetRetriever)

override fun publicationRetriever(
downloadManagerProvider: DownloadManagerProvider,
listener: LcpPublicationRetriever.Listener
): LcpPublicationRetriever {
return LcpPublicationRetriever(
Expand All @@ -86,6 +86,11 @@ internal class LicensesService(
)
}

@Deprecated(
"Use a LcpPublicationRetriever instead.",
ReplaceWith("publicationRetriever()"),
level = DeprecationLevel.ERROR
)
override suspend fun acquirePublication(lcpl: ByteArray, onProgress: (Double) -> Unit): Try<LcpService.AcquiredPublication, LcpException> =
try {
val licenseDocument = LicenseDocument(lcpl)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright 2023 Readium Foundation. All rights reserved.
* Use of this source code is governed by the BSD-style license
* available in the top-level LICENSE file of the project.
*/

package org.readium.r2.shared.util.downloads.foreground

import java.io.File
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.readium.r2.shared.util.ThrowableError
import org.readium.r2.shared.util.Try
import org.readium.r2.shared.util.downloads.DownloadManager
import org.readium.r2.shared.util.http.HttpClient
import org.readium.r2.shared.util.http.HttpException
import org.readium.r2.shared.util.http.HttpRequest

public class ForegroundDownloadManager(
private val httpClient: HttpClient,
private val listener: DownloadManager.Listener
) : DownloadManager {

private val coroutineScope: CoroutineScope =
MainScope()

private val jobs: MutableMap<DownloadManager.RequestId, Job> =
mutableMapOf()

override suspend fun submit(request: DownloadManager.Request): DownloadManager.RequestId {
val requestId = DownloadManager.RequestId(UUID.randomUUID().toString())
jobs[requestId] = coroutineScope.launch { doRequest(request, requestId) }
return requestId
}

private suspend fun doRequest(request: DownloadManager.Request, id: DownloadManager.RequestId) {
val response = httpClient.fetch(
HttpRequest(
url = request.url.toString(),
headers = request.headers.mapValues { it.value.joinToString(",") }
)
)

val dottedExtension = request.url.extension
?.let { ".$it" }
.orEmpty()

when (response) {
is Try.Success -> {
withContext(Dispatchers.IO) {
try {
val dest = File.createTempFile(
UUID.randomUUID().toString(),
dottedExtension
)
dest.writeBytes(response.value.body)
} catch (e: Exception) {
val error = DownloadManager.Error.FileError(ThrowableError(e))
listener.onDownloadFailed(id, error)
}
}
}
is Try.Failure -> {
val error = mapError(response.value)
listener.onDownloadFailed(id, error)
}
}
}

private fun mapError(httpException: HttpException): DownloadManager.Error {
val httpError = ThrowableError(httpException)
return when (httpException.kind) {
HttpException.Kind.MalformedRequest ->
DownloadManager.Error.Unknown(httpError)
HttpException.Kind.MalformedResponse ->
DownloadManager.Error.HttpData(httpError)
HttpException.Kind.Timeout ->
DownloadManager.Error.Unreachable(httpError)
HttpException.Kind.BadRequest ->
DownloadManager.Error.Unknown(httpError)
HttpException.Kind.Unauthorized ->
DownloadManager.Error.Forbidden(httpError)
HttpException.Kind.Forbidden ->
DownloadManager.Error.Forbidden(httpError)
HttpException.Kind.NotFound ->
DownloadManager.Error.NotFound(httpError)
HttpException.Kind.ClientError ->
DownloadManager.Error.HttpData(httpError)
HttpException.Kind.ServerError ->
DownloadManager.Error.Server(httpError)
HttpException.Kind.Offline ->
DownloadManager.Error.Unreachable(httpError)
HttpException.Kind.Cancelled ->
DownloadManager.Error.Unknown(httpError)
HttpException.Kind.Other ->
DownloadManager.Error.Unknown(httpError)
}
}

override suspend fun cancel(requestId: DownloadManager.RequestId) {
jobs.remove(requestId)?.cancel()
}

override suspend fun close() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2023 Readium Foundation. All rights reserved.
* Use of this source code is governed by the BSD-style license
* available in the top-level LICENSE file of the project.
*/

package org.readium.r2.shared.util.downloads.foreground

import org.readium.r2.shared.util.downloads.DownloadManager
import org.readium.r2.shared.util.downloads.DownloadManagerProvider
import org.readium.r2.shared.util.http.HttpClient

public class ForegroundDownloadManagerProvider(
private val httpClient: HttpClient
) : DownloadManagerProvider {

override fun createDownloadManager(
listener: DownloadManager.Listener,
name: String
): DownloadManager {
return ForegroundDownloadManager(httpClient, listener)
}
}
3 changes: 2 additions & 1 deletion test-app/src/main/java/org/readium/r2/testapp/Readium.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ class Readium(context: Context) {
val lcpService = LcpService(
context,
assetRetriever,
mediaTypeRetriever
mediaTypeRetriever,
downloadManagerProvider
)?.let { Try.success(it) }
?: Try.failure(UserException("liblcp is missing on the classpath"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class Bookshelf(
private val assetRetriever: AssetRetriever,
private val protectionRetriever: ContentProtectionSchemeRetriever,
private val formatRegistry: FormatRegistry,
private val downloadManagerProvider: DownloadManagerProvider
downloadManagerProvider: DownloadManagerProvider
) {
sealed class ImportError(
content: Content,
Expand Down Expand Up @@ -162,7 +162,6 @@ class Bookshelf(

private val lcpPublicationRetriever = lcpService.map {
it.publicationRetriever(
downloadManagerProvider,
LcpRetrieverListener()
)
}
Expand Down

0 comments on commit fd9af69

Please sign in to comment.