Skip to content

Commit 501081a

Browse files
authored
Make Lcp license API asynchronous (#414)
1 parent 28b073e commit 501081a

File tree

10 files changed

+172
-106
lines changed

10 files changed

+172
-106
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public class LcpContentProtectionService(
2727

2828
override val scheme: ContentProtection.Scheme = ContentProtection.Scheme.Lcp
2929

30+
override fun close() {
31+
license?.close()
32+
}
33+
3034
public companion object {
3135

3236
public fun createFactory(license: LcpLicense?, error: LcpException?): (

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,22 @@ import java.net.URL
1010
import java.util.*
1111
import kotlinx.coroutines.DelicateCoroutinesApi
1212
import kotlinx.coroutines.GlobalScope
13+
import kotlinx.coroutines.flow.StateFlow
1314
import kotlinx.coroutines.launch
1415
import kotlinx.coroutines.runBlocking
1516
import org.joda.time.DateTime
1617
import org.readium.r2.lcp.license.model.LicenseDocument
1718
import org.readium.r2.lcp.license.model.StatusDocument
1819
import org.readium.r2.shared.publication.services.ContentProtectionService
20+
import org.readium.r2.shared.util.Closeable
1921
import org.readium.r2.shared.util.Try
2022
import org.readium.r2.shared.util.Url
2123
import timber.log.Timber
2224

2325
/**
2426
* Opened license, used to decipher a protected publication and manage its license.
2527
*/
26-
public interface LcpLicense : ContentProtectionService.UserRights {
28+
public interface LcpLicense : ContentProtectionService.UserRights, Closeable {
2729

2830
/**
2931
* License Document information.
@@ -40,12 +42,12 @@ public interface LcpLicense : ContentProtectionService.UserRights {
4042
/**
4143
* Number of remaining characters allowed to be copied by the user. If null, there's no limit.
4244
*/
43-
public val charactersToCopyLeft: Int?
45+
public val charactersToCopyLeft: StateFlow<Int?>
4446

4547
/**
4648
* Number of pages allowed to be printed by the user. If null, there's no limit.
4749
*/
48-
public val pagesToPrintLeft: Int?
50+
public val pagesToPrintLeft: StateFlow<Int?>
4951

5052
/**
5153
* Can the user renew the loaned publication?

readium/lcp/src/main/java/org/readium/r2/lcp/license/License.kt

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ package org.readium.r2.lcp.license
1212
import java.net.HttpURLConnection
1313
import java.util.*
1414
import kotlinx.coroutines.CancellationException
15+
import kotlinx.coroutines.CoroutineScope
1516
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.MainScope
18+
import kotlinx.coroutines.cancel
19+
import kotlinx.coroutines.flow.StateFlow
20+
import kotlinx.coroutines.flow.stateIn
1621
import kotlinx.coroutines.withContext
1722
import org.readium.r2.lcp.BuildConfig.DEBUG
1823
import org.readium.r2.lcp.LcpException
@@ -32,14 +37,49 @@ import org.readium.r2.shared.util.getOrThrow
3237
import org.readium.r2.shared.util.mediatype.MediaType
3338
import timber.log.Timber
3439

35-
internal class License(
40+
internal class License private constructor(
41+
private val coroutineScope: CoroutineScope,
3642
private var documents: ValidatedDocuments,
3743
private val validation: LicenseValidation,
3844
private val licenses: LicensesRepository,
3945
private val device: DeviceService,
40-
private val network: NetworkService
46+
private val network: NetworkService,
47+
private val printsLeft: StateFlow<Int?>,
48+
private val copiesLeft: StateFlow<Int?>
4149
) : LcpLicense {
4250

51+
companion object {
52+
53+
suspend operator fun invoke(
54+
documents: ValidatedDocuments,
55+
validation: LicenseValidation,
56+
licenses: LicensesRepository,
57+
device: DeviceService,
58+
network: NetworkService
59+
): License {
60+
val coroutineScope = MainScope()
61+
62+
val printsLeft = licenses
63+
.printsLeft(documents.license.id)
64+
.stateIn(coroutineScope)
65+
66+
val copiesLeft = licenses
67+
.copiesLeft(documents.license.id)
68+
.stateIn(coroutineScope)
69+
70+
return License(
71+
coroutineScope = coroutineScope,
72+
documents = documents,
73+
validation = validation,
74+
licenses = licenses,
75+
device = device,
76+
network = network,
77+
printsLeft = printsLeft,
78+
copiesLeft = copiesLeft
79+
)
80+
}
81+
}
82+
4383
override val license: LicenseDocument
4484
get() = documents.license
4585
override val status: StatusDocument?
@@ -62,73 +102,42 @@ internal class License(
62102
}
63103
}
64104

65-
override val charactersToCopyLeft: Int?
66-
get() {
67-
try {
68-
val charactersLeft = licenses.copiesLeft(license.id)
69-
if (charactersLeft != null) {
70-
return charactersLeft
71-
}
72-
} catch (error: Error) {
73-
if (DEBUG) Timber.e(error)
74-
}
75-
return null
76-
}
105+
override val charactersToCopyLeft: StateFlow<Int?>
106+
get() = copiesLeft
77107

78108
override val canCopy: Boolean
79-
get() = (charactersToCopyLeft ?: 1) > 0
109+
get() = (charactersToCopyLeft.value ?: 1) > 0
80110

81111
override fun canCopy(text: String): Boolean =
82-
charactersToCopyLeft?.let { it <= text.length }
112+
charactersToCopyLeft.value?.let { it <= text.length }
83113
?: true
84114

85-
override fun copy(text: String): Boolean {
86-
var charactersLeft = charactersToCopyLeft ?: return true
87-
if (text.length > charactersLeft) {
88-
return false
89-
}
90-
91-
try {
92-
charactersLeft = maxOf(0, charactersLeft - text.length)
93-
licenses.setCopiesLeft(charactersLeft, license.id)
94-
} catch (error: Error) {
95-
if (DEBUG) Timber.e(error)
115+
override suspend fun copy(text: String): Boolean {
116+
return try {
117+
licenses.tryCopy(text.length, license.id)
118+
} catch (e: Exception) {
119+
if (DEBUG) Timber.e(e)
120+
false
96121
}
97-
return true
98122
}
99123

100-
override val pagesToPrintLeft: Int?
101-
get() {
102-
try {
103-
val pagesLeft = licenses.printsLeft(license.id)
104-
if (pagesLeft != null) {
105-
return pagesLeft
106-
}
107-
} catch (error: Error) {
108-
if (DEBUG) Timber.e(error)
109-
}
110-
return null
111-
}
124+
override val pagesToPrintLeft: StateFlow<Int?> =
125+
printsLeft
112126

113127
override val canPrint: Boolean
114-
get() = (pagesToPrintLeft ?: 1) > 0
128+
get() = (pagesToPrintLeft.value ?: 1) > 0
115129

116130
override fun canPrint(pageCount: Int): Boolean =
117-
pagesToPrintLeft?.let { it <= pageCount }
131+
pagesToPrintLeft.value?.let { it <= pageCount }
118132
?: true
119133

120-
override fun print(pageCount: Int): Boolean {
121-
var pagesLeft = pagesToPrintLeft ?: return true
122-
if (pagesLeft < pageCount) {
123-
return false
124-
}
125-
try {
126-
pagesLeft = maxOf(0, pagesLeft - pageCount)
127-
licenses.setPrintsLeft(pagesLeft, license.id)
128-
} catch (error: Error) {
129-
if (DEBUG) Timber.e(error)
134+
override suspend fun print(pageCount: Int): Boolean {
135+
return try {
136+
licenses.tryPrint(pageCount, license.id)
137+
} catch (e: Exception) {
138+
if (DEBUG) Timber.e(e)
139+
false
130140
}
131-
return true
132141
}
133142

134143
override val canRenewLoan: Boolean
@@ -261,6 +270,9 @@ internal class License(
261270
}
262271
}
263272

273+
private fun validateStatusDocument(data: ByteArray): Unit =
274+
validation.validate(LicenseValidation.Document.status(data)) { _, _ -> }
275+
264276
init {
265277
LicenseValidation.observe(validation) { documents, _ ->
266278
documents?.let {
@@ -269,6 +281,7 @@ internal class License(
269281
}
270282
}
271283

272-
private fun validateStatusDocument(data: ByteArray): Unit =
273-
validation.validate(LicenseValidation.Document.status(data)) { _, _ -> }
284+
override fun close() {
285+
coroutineScope.cancel()
286+
}
274287
}

readium/lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDao.kt

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import androidx.room.Dao
44
import androidx.room.Insert
55
import androidx.room.OnConflictStrategy
66
import androidx.room.Query
7+
import androidx.room.Transaction
8+
import kotlinx.coroutines.flow.Flow
79

810
@Dao
911
internal interface LcpDao {
@@ -49,20 +51,62 @@ internal interface LcpDao {
4951
@Query(
5052
"SELECT ${License.RIGHTCOPY} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId"
5153
)
52-
fun getCopiesLeft(licenseId: String): Int?
54+
suspend fun getCopiesLeft(licenseId: String): Int?
55+
56+
@Query(
57+
"SELECT ${License.RIGHTCOPY} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId"
58+
)
59+
fun copiesLeftFlow(licenseId: String): Flow<Int?>
5360

5461
@Query(
5562
"UPDATE ${License.TABLE_NAME} SET ${License.RIGHTCOPY} = :quantity WHERE ${License.LICENSE_ID} = :licenseId"
5663
)
57-
fun setCopiesLeft(quantity: Int, licenseId: String)
64+
suspend fun setCopiesLeft(quantity: Int, licenseId: String)
65+
66+
@Transaction
67+
suspend fun tryCopy(quantity: Int, licenseId: String): Boolean {
68+
require(quantity >= 0)
69+
val copiesLeft = getCopiesLeft(licenseId)
70+
return when {
71+
copiesLeft == null ->
72+
true
73+
copiesLeft < quantity ->
74+
false
75+
else -> {
76+
setCopiesLeft(copiesLeft - quantity, licenseId)
77+
return true
78+
}
79+
}
80+
}
81+
82+
@Query(
83+
"SELECT ${License.RIGHTPRINT} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId"
84+
)
85+
suspend fun getPrintsLeft(licenseId: String): Int?
5886

5987
@Query(
6088
"SELECT ${License.RIGHTPRINT} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId"
6189
)
62-
fun getPrintsLeft(licenseId: String): Int?
90+
fun printsLeftFlow(licenseId: String): Flow<Int?>
6391

6492
@Query(
6593
"UPDATE ${License.TABLE_NAME} SET ${License.RIGHTPRINT} = :quantity WHERE ${License.LICENSE_ID} = :licenseId"
6694
)
67-
fun setPrintsLeft(quantity: Int, licenseId: String)
95+
suspend fun setPrintsLeft(quantity: Int, licenseId: String)
96+
97+
@Transaction
98+
suspend fun tryPrint(quantity: Int, licenseId: String): Boolean {
99+
require(quantity >= 0)
100+
val printLeft = getPrintsLeft(licenseId)
101+
return when {
102+
printLeft == null ->
103+
true
104+
printLeft < quantity ->
105+
false
106+
else -> {
107+
setPrintsLeft(printLeft - quantity, licenseId)
108+
return true
109+
}
110+
}
111+
}
68112
}

readium/lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDatabase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ internal abstract class LcpDatabase : RoomDatabase() {
8181
context.applicationContext,
8282
LcpDatabase::class.java,
8383
"lcpdatabase"
84-
).allowMainThreadQueries().addMigrations(MIGRATION_1_2).build()
84+
).addMigrations(MIGRATION_1_2).build()
8585
INSTANCE = instance
8686
return instance
8787
}

readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesRepository.kt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
package org.readium.r2.lcp.service
1111

12+
import kotlinx.coroutines.flow.Flow
1213
import org.readium.r2.lcp.license.model.LicenseDocument
1314
import org.readium.r2.lcp.persistence.LcpDao
1415
import org.readium.r2.lcp.persistence.License
@@ -27,19 +28,17 @@ internal class LicensesRepository(private val lcpDao: LcpDao) {
2728
lcpDao.addLicense(license)
2829
}
2930

30-
fun copiesLeft(licenseId: String): Int? {
31-
return lcpDao.getCopiesLeft(licenseId)
31+
fun copiesLeft(licenseId: String): Flow<Int?> {
32+
return lcpDao.copiesLeftFlow(licenseId)
3233
}
3334

34-
fun setCopiesLeft(quantity: Int, licenseId: String) {
35-
lcpDao.setCopiesLeft(quantity, licenseId)
36-
}
35+
suspend fun tryCopy(quantity: Int, licenseId: String): Boolean =
36+
lcpDao.tryCopy(quantity, licenseId)
3737

38-
fun printsLeft(licenseId: String): Int? {
39-
return lcpDao.getPrintsLeft(licenseId)
38+
fun printsLeft(licenseId: String): Flow<Int?> {
39+
return lcpDao.printsLeftFlow(licenseId)
4040
}
4141

42-
fun setPrintsLeft(quantity: Int, licenseId: String) {
43-
lcpDao.setPrintsLeft(quantity, licenseId)
44-
}
42+
suspend fun tryPrint(quantity: Int, licenseId: String): Boolean =
43+
lcpDao.tryPrint(quantity, licenseId)
4544
}

0 commit comments

Comments
 (0)