Skip to content

Commit a0cfa4c

Browse files
committed
Adjust LCP downloads
1 parent 16110a4 commit a0cfa4c

File tree

4 files changed

+104
-23
lines changed

4 files changed

+104
-23
lines changed

readium/lcp/src/main/java/org/readium/r2/lcp/LcpDownloadsRepository.kt

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,67 +9,60 @@ package org.readium.r2.lcp
99
import android.content.Context
1010
import java.io.File
1111
import java.util.LinkedList
12-
import kotlinx.coroutines.CoroutineScope
1312
import kotlinx.coroutines.Deferred
1413
import kotlinx.coroutines.Dispatchers
15-
import kotlinx.coroutines.MainScope
1614
import kotlinx.coroutines.async
17-
import kotlinx.coroutines.job
1815
import kotlinx.coroutines.launch
19-
import kotlinx.coroutines.plus
2016
import kotlinx.coroutines.withContext
2117
import org.json.JSONObject
18+
import org.readium.r2.shared.util.CoroutineQueue
2219

2320
internal class LcpDownloadsRepository(
2421
context: Context
2522
) {
26-
private val coroutineScope: CoroutineScope =
27-
MainScope()
23+
private val queue = CoroutineQueue()
2824

2925
private val storageDir: Deferred<File> =
30-
coroutineScope.async {
26+
queue.scope.async {
3127
withContext(Dispatchers.IO) {
3228
File(context.noBackupFilesDir, LcpDownloadsRepository::class.qualifiedName!!)
3329
.also { if (!it.exists()) it.mkdirs() }
3430
}
3531
}
3632

3733
private val storageFile: Deferred<File> =
38-
coroutineScope.async {
34+
queue.scope.async {
3935
withContext(Dispatchers.IO) {
4036
File(storageDir.await(), "licenses.json")
4137
.also { if (!it.exists()) { it.writeText("{}", Charsets.UTF_8) } }
4238
}
4339
}
4440

4541
private val snapshot: Deferred<MutableMap<String, JSONObject>> =
46-
coroutineScope.async {
42+
queue.scope.async {
4743
readSnapshot().toMutableMap()
4844
}
4945

5046
fun addDownload(id: String, license: JSONObject) {
51-
coroutineScope.launch {
47+
queue.scope.launch {
5248
val snapshotCompleted = snapshot.await()
5349
snapshotCompleted[id] = license
5450
writeSnapshot(snapshotCompleted)
5551
}
5652
}
5753

5854
fun removeDownload(id: String) {
59-
coroutineScope.launch {
55+
queue.launch {
6056
val snapshotCompleted = snapshot.await()
6157
snapshotCompleted.remove(id)
6258
writeSnapshot(snapshotCompleted)
6359
}
6460
}
6561

66-
suspend fun retrieveLicense(id: String): JSONObject? {
67-
val snapshot = snapshot.await()
68-
while (coroutineScope.coroutineContext.job.children.toList().isNotEmpty()) {
69-
coroutineScope.coroutineContext.job.children.forEach { it.join() }
62+
suspend fun retrieveLicense(id: String): JSONObject? =
63+
queue.await {
64+
snapshot.await()[id]
7065
}
71-
return snapshot[id]
72-
}
7366

7467
private suspend fun readSnapshot(): Map<String, JSONObject> {
7568
return withContext(Dispatchers.IO) {

readium/lcp/src/main/java/org/readium/r2/lcp/LcpPublicationRetriever.kt

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public class LcpPublicationRetriever(
8585
downloadTitle,
8686
downloadDescription
8787
)
88-
register(requestId, listener)
88+
addListener(requestId, listener)
8989
return requestId
9090
}
9191

@@ -99,10 +99,16 @@ public class LcpPublicationRetriever(
9999
requestId: RequestId,
100100
listener: Listener
101101
) {
102-
listeners.getOrPut(requestId) {
103-
downloadManager.register(DownloadManager.RequestId(requestId.value), downloadListener)
104-
mutableListOf()
105-
}.add(listener)
102+
addListener(
103+
requestId,
104+
listener,
105+
onFirstListenerAdded = {
106+
downloadManager.register(
107+
DownloadManager.RequestId(requestId.value),
108+
downloadListener
109+
)
110+
}
111+
)
106112
}
107113

108114
/**
@@ -137,6 +143,19 @@ public class LcpPublicationRetriever(
137143
private val listeners: MutableMap<RequestId, MutableList<Listener>> =
138144
mutableMapOf()
139145

146+
private fun addListener(
147+
requestId: RequestId,
148+
listener: Listener,
149+
onFirstListenerAdded: () -> Unit = {}
150+
) {
151+
listeners
152+
.getOrPut(requestId) {
153+
onFirstListenerAdded()
154+
mutableListOf()
155+
}
156+
.add(listener)
157+
}
158+
140159
private fun fetchPublication(
141160
license: LicenseDocument,
142161
downloadTitle: String,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2023 Readium Foundation. All rights reserved.
3+
* Use of this source code is governed by the BSD-style license
4+
* available in the top-level LICENSE file of the project.
5+
*/
6+
7+
package org.readium.r2.shared.util
8+
9+
import kotlin.coroutines.resume
10+
import kotlinx.coroutines.CancellableContinuation
11+
import kotlinx.coroutines.CancellationException
12+
import kotlinx.coroutines.CoroutineScope
13+
import kotlinx.coroutines.MainScope
14+
import kotlinx.coroutines.cancel
15+
import kotlinx.coroutines.channels.Channel
16+
import kotlinx.coroutines.channels.trySendBlocking
17+
import kotlinx.coroutines.launch
18+
import kotlinx.coroutines.suspendCancellableCoroutine
19+
import org.readium.r2.shared.InternalReadiumApi
20+
21+
/**
22+
* Executes coroutines in a sequential order (FIFO).
23+
*/
24+
@InternalReadiumApi
25+
public class CoroutineQueue(
26+
public val scope: CoroutineScope = MainScope()
27+
) {
28+
init {
29+
scope.launch {
30+
for (task in tasks) {
31+
task()
32+
}
33+
}
34+
}
35+
36+
/**
37+
* Launches a coroutine in the queue.
38+
*/
39+
public fun launch(task: suspend () -> Unit) {
40+
tasks.trySendBlocking(Task(task)).getOrThrow()
41+
}
42+
43+
/**
44+
* Launches a coroutine in the queue, and waits for its result.
45+
*/
46+
public suspend fun <T> await(task: suspend () -> T): T =
47+
suspendCancellableCoroutine { cont ->
48+
tasks.trySendBlocking(Task(task, cont)).getOrThrow()
49+
}
50+
51+
/**
52+
* Cancels all the coroutines in the queue.
53+
*/
54+
public fun cancel(cause: CancellationException? = null) {
55+
scope.cancel(cause)
56+
}
57+
58+
private val tasks: Channel<Task<*>> = Channel(Channel.UNLIMITED)
59+
60+
private class Task<T>(
61+
val task: suspend () -> T,
62+
val continuation: CancellableContinuation<T>? = null
63+
) {
64+
suspend operator fun invoke() {
65+
val result = task()
66+
continuation?.resume(result)
67+
}
68+
}
69+
}

readium/shared/src/main/java/org/readium/r2/shared/util/downloads/android/AndroidDownloadManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ public class AndroidDownloadManager internal constructor(
248248
maybeStopObservingProgress()
249249
}
250250
SystemDownloadManager.STATUS_RUNNING -> {
251-
val expected = facade.expected
251+
val expected = facade.expected?.takeIf { it > 0 }
252252
listenersForId.forEach {
253253
it.onDownloadProgressed(id, facade.downloadedSoFar, expected)
254254
}

0 commit comments

Comments
 (0)