Skip to content

Commit 4df5df0

Browse files
authored
Migrate from Anko to Room (#116)
1 parent 0b106b1 commit 4df5df0

File tree

17 files changed

+318
-293
lines changed

17 files changed

+318
-293
lines changed

readium/lcp/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ All notable changes to this project will be documented in this file.
88

99
### Changed
1010

11-
* Upgraded to Kotlin 1.5.21 and Gradle 7.1.1
11+
* Upgraded to Kotlin 1.5.21 and Gradle 7.1.1.
12+
* Migrated to Jetpack Room for the SQLite database storing rights and passphrases (contributed by [@stevenzeck](https://github.com/readium/r2-lcp-kotlin/pull/116)).
13+
* Note that the internal SQL schema changed. You will need to update your app if you were querying the database manually.
1214

1315

1416
## [2.0.0]

readium/lcp/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ buildscript {
55

66
repositories {
77
google()
8-
jcenter()
8+
mavenCentral()
99
}
1010
dependencies {
1111
classpath 'com.android.tools.build:gradle:7.0.0'
@@ -20,7 +20,7 @@ buildscript {
2020
allprojects {
2121
repositories {
2222
google()
23-
jcenter()
23+
mavenCentral()
2424
maven { url 'https://jitpack.io' }
2525
}
2626
}

readium/lcp/r2-lcp/build.gradle

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ plugins {
88
id 'com.android.library'
99
id 'kotlin-android'
1010
id 'kotlin-parcelize'
11+
id 'kotlin-kapt'
1112
id 'maven-publish'
1213
}
1314

@@ -50,7 +51,7 @@ afterEvaluate {
5051

5152
dependencies {
5253
implementation fileTree(include: ['*.jar'], dir: 'libs')
53-
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-native-mt"
54+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0"
5455

5556
if (findProject(':r2-shared')) {
5657
implementation project(':r2-shared')
@@ -70,10 +71,14 @@ dependencies {
7071
exclude module: 'support-v4'
7172
}
7273
implementation "joda-time:joda-time:2.10.10"
73-
implementation "org.jetbrains.anko:anko-sqlite:0.10.8"
7474
implementation "org.zeroturnaround:zt-zip:1.14"
7575
implementation 'androidx.browser:browser:1.3.0'
7676

77+
final room_version = '2.4.0-alpha04'
78+
implementation "androidx.room:room-runtime:$room_version"
79+
implementation "androidx.room:room-ktx:$room_version"
80+
kapt "androidx.room:room-compiler:$room_version"
81+
7782
testImplementation "junit:junit:4.13.2"
7883
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
7984

readium/lcp/r2-lcp/src/main/java/org/readium/r2/lcp/LcpService.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ package org.readium.r2.lcp
1212
import android.content.Context
1313
import kotlinx.coroutines.*
1414
import org.readium.r2.lcp.auth.LcpDialogAuthentication
15-
import org.readium.r2.lcp.persistence.Database
15+
import org.readium.r2.lcp.persistence.LcpDatabase
1616
import org.readium.r2.lcp.service.*
1717
import org.readium.r2.shared.publication.ContentProtection
1818
import org.readium.r2.shared.util.Try
@@ -107,12 +107,15 @@ interface LcpService {
107107
if (!LcpClient.isAvailable())
108108
return null
109109

110-
val db = Database(context)
110+
val db = LcpDatabase.getDatabase(context).lcpDao()
111+
val deviceRepository = DeviceRepository(db)
112+
val passphraseRepository = PassphrasesRepository(db)
113+
val licenseRepository = LicensesRepository(db)
111114
val network = NetworkService()
112-
val device = DeviceService(repository = db.licenses, network = network, context = context)
115+
val device = DeviceService(repository = deviceRepository, network = network, context = context)
113116
val crl = CRLService(network = network, context = context)
114-
val passphrases = PassphrasesService(repository = db.transactions)
115-
return LicensesService(licenses = db.licenses, crl = crl, device = device, network = network, passphrases = passphrases, context = context)
117+
val passphrases = PassphrasesService(repository = passphraseRepository)
118+
return LicensesService(licenses = licenseRepository, crl = crl, device = device, network = network, passphrases = passphrases, context = context)
116119
}
117120

118121
@Deprecated("Use `LcpService()` instead", ReplaceWith("LcpService(context)"), level = DeprecationLevel.ERROR)

readium/lcp/r2-lcp/src/main/java/org/readium/r2/lcp/auth/LcpDialogAuthentication.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import android.net.Uri
1414
import android.view.Gravity
1515
import android.view.LayoutInflater
1616
import android.view.View
17+
import android.view.ViewGroup
1718
import android.widget.Button
1819
import android.widget.ListPopupWindow
1920
import android.widget.PopupWindow
@@ -25,7 +26,6 @@ import com.google.android.material.textfield.TextInputEditText
2526
import com.google.android.material.textfield.TextInputLayout
2627
import kotlinx.coroutines.Dispatchers
2728
import kotlinx.coroutines.withContext
28-
import org.jetbrains.anko.contentView
2929
import org.readium.r2.lcp.LcpAuthenticating
3030
import org.readium.r2.lcp.R
3131
import org.readium.r2.lcp.license.model.components.Link
@@ -55,7 +55,7 @@ class LcpDialogAuthentication : LcpAuthenticating {
5555
else null
5656

5757
private suspend fun askPassphrase(license: LcpAuthenticating.AuthenticatedLicense, reason: LcpAuthenticating.AuthenticationReason, sender: Any?): String? {
58-
val hostView = (sender as? View) ?: (sender as? Activity)?.contentView ?: (sender as? Fragment)?.view
58+
val hostView = (sender as? View) ?: (sender as? Activity)?.findViewById<ViewGroup>(android.R.id.content)?.getChildAt(0) ?: (sender as? Fragment)?.view
5959
?: run {
6060
Timber.e("No valid [sender] was passed to `LcpDialogAuthentication::retrievePassphrase()`. Make sure it is an Activity, a Fragment or a View.")
6161
return null

readium/lcp/r2-lcp/src/main/java/org/readium/r2/lcp/persistence/Database.kt

Lines changed: 70 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,64 +10,83 @@
1010
package org.readium.r2.lcp.persistence
1111

1212
import android.content.Context
13-
import android.database.sqlite.SQLiteDatabase
14-
import org.jetbrains.anko.db.INTEGER
15-
import org.jetbrains.anko.db.ManagedSQLiteOpenHelper
16-
import org.jetbrains.anko.db.TEXT
17-
import org.jetbrains.anko.db.createTable
13+
import androidx.room.Database
14+
import androidx.room.Room
15+
import androidx.room.RoomDatabase
16+
import androidx.room.migration.Migration
17+
import androidx.sqlite.db.SupportSQLiteDatabase
1818

1919

20-
// Access property for Context
21-
internal val Context.database: LcpDatabaseOpenHelper
22-
get() = LcpDatabaseOpenHelper.getInstance(applicationContext)
20+
@Database(
21+
entities = [Passphrase::class, License::class],
22+
version = 2,
23+
exportSchema = false
24+
)
25+
internal abstract class LcpDatabase : RoomDatabase() {
2326

24-
internal val Context.appContext: Context
25-
get() = applicationContext
27+
abstract fun lcpDao(): LcpDao
2628

27-
28-
internal class Database(context: Context) {
29-
30-
val shared: LcpDatabaseOpenHelper = LcpDatabaseOpenHelper(context)
31-
var licenses: Licenses
32-
var transactions: Transactions
33-
34-
init {
35-
licenses = Licenses(shared)
36-
transactions = Transactions(shared)
37-
}
38-
39-
}
40-
41-
internal class LcpDatabaseOpenHelper(ctx: Context) : ManagedSQLiteOpenHelper(ctx, "lcpdatabase", null, 1) {
4229
companion object {
43-
private var instance: LcpDatabaseOpenHelper? = null
30+
@Volatile
31+
private var INSTANCE: LcpDatabase? = null
4432

45-
@Synchronized
46-
fun getInstance(ctx: Context): LcpDatabaseOpenHelper {
47-
if (instance == null) {
48-
instance = LcpDatabaseOpenHelper(ctx.applicationContext)
33+
fun getDatabase(context: Context): LcpDatabase {
34+
val tempInstance = INSTANCE
35+
if (tempInstance != null) {
36+
return tempInstance
37+
}
38+
val MIGRATION_1_2 = object : Migration(1, 2) {
39+
override fun migrate(database: SupportSQLiteDatabase) {
40+
database.execSQL(
41+
"""
42+
CREATE TABLE passphrases (
43+
id INTEGER PRIMARY KEY AUTOINCREMENT,
44+
license_id TEXT,
45+
provider TEXT,
46+
user_id TEXT,
47+
passphrase TEXT NOT NULL
48+
)
49+
""".trimIndent()
50+
)
51+
database.execSQL(
52+
"""
53+
INSERT INTO passphrases (license_id, provider, user_id, passphrase)
54+
SELECT id, origin, userId, passphrase FROM Transactions
55+
""".trimIndent()
56+
)
57+
database.execSQL("DROP TABLE Transactions")
58+
59+
60+
database.execSQL(
61+
"""
62+
CREATE TABLE new_Licenses (
63+
id INTEGER PRIMARY KEY AUTOINCREMENT,
64+
license_id TEXT NOT NULL,
65+
right_print INTEGER,
66+
right_copy INTEGER,
67+
registered INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0
68+
)
69+
""".trimIndent()
70+
)
71+
database.execSQL(
72+
"""
73+
INSERT INTO new_Licenses (license_id, right_print, right_copy, registered)
74+
SELECT id, printsLeft, copiesLeft, registered FROM Licenses
75+
""".trimIndent()
76+
)
77+
database.execSQL("DROP TABLE Licenses")
78+
database.execSQL("ALTER TABLE new_Licenses RENAME TO licenses")
79+
}
80+
}
81+
synchronized(this) {
82+
val instance = Room.databaseBuilder(
83+
context.applicationContext,
84+
LcpDatabase::class.java,
85+
"lcpdatabase"
86+
).allowMainThreadQueries().addMigrations(MIGRATION_1_2).build()
87+
INSTANCE = instance
88+
return instance
4989
}
50-
return instance!!
5190
}
5291
}
53-
54-
override fun onCreate(db: SQLiteDatabase) {
55-
56-
db.createTable(LicensesTable.NAME, true,
57-
LicensesTable.ID to TEXT,
58-
LicensesTable.PRINTSLEFT to INTEGER,
59-
LicensesTable.COPIESLEFT to INTEGER,
60-
LicensesTable.REGISTERED to INTEGER)
61-
62-
db.createTable(TransactionsTable.NAME, true,
63-
TransactionsTable.ID to TEXT,
64-
TransactionsTable.ORIGIN to TEXT,
65-
TransactionsTable.USERID to TEXT,
66-
TransactionsTable.PASSPHRASE to TEXT)
67-
68-
}
69-
70-
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
71-
// Here you can upgrade tables, as usual
72-
}
7392
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.readium.r2.lcp.persistence
2+
3+
import androidx.room.Dao
4+
import androidx.room.Insert
5+
import androidx.room.OnConflictStrategy
6+
import androidx.room.Query
7+
8+
@Dao
9+
interface LcpDao {
10+
11+
/**
12+
* Retrieve passphrase
13+
* @return Passphrase
14+
*/
15+
@Query("SELECT ${Passphrase.PASSPHRASE} FROM ${Passphrase.TABLE_NAME} WHERE ${Passphrase.PROVIDER} = :licenseId")
16+
suspend fun passphrase(licenseId: String): String?
17+
18+
@Query("SELECT ${Passphrase.PASSPHRASE} FROM ${Passphrase.TABLE_NAME} WHERE ${Passphrase.USERID} = :userId")
19+
suspend fun passphrases(userId: String): List<String>
20+
21+
@Query("SELECT ${Passphrase.PASSPHRASE} FROM ${Passphrase.TABLE_NAME}")
22+
suspend fun allPassphrases(): List<String>
23+
24+
@Insert(onConflict = OnConflictStrategy.IGNORE)
25+
suspend fun addPassphrase(passphrase: Passphrase)
26+
27+
@Query("SELECT ${License.LICENSE_ID} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId")
28+
suspend fun exists(licenseId: String): String?
29+
30+
@Query("SELECT ${License.REGISTERED} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId")
31+
suspend fun isDeviceRegistered(licenseId: String): Boolean
32+
33+
@Query("UPDATE ${License.TABLE_NAME} SET ${License.REGISTERED} = 1 WHERE ${License.LICENSE_ID} = :licenseId")
34+
suspend fun registerDevice(licenseId: String)
35+
36+
@Insert(onConflict = OnConflictStrategy.IGNORE)
37+
suspend fun addLicense(license: License)
38+
39+
@Query("SELECT ${License.RIGHTCOPY} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId")
40+
fun getCopiesLeft(licenseId: String): Int?
41+
42+
@Query("UPDATE ${License.TABLE_NAME} SET ${License.RIGHTCOPY} = :quantity WHERE ${License.LICENSE_ID} = :licenseId")
43+
fun setCopiesLeft(quantity: Int, licenseId: String)
44+
45+
@Query("SELECT ${License.RIGHTPRINT} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId")
46+
fun getPrintsLeft(licenseId: String): Int?
47+
48+
@Query("UPDATE ${License.TABLE_NAME} SET ${License.RIGHTPRINT} = :quantity WHERE ${License.LICENSE_ID} = :licenseId")
49+
fun setPrintsLeft(quantity: Int, licenseId: String)
50+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Module: r2-lcp-kotlin
3+
* Developers: Aferdita Muriqi
4+
*
5+
* Copyright (c) 2019. Readium Foundation. All rights reserved.
6+
* Use of this source code is governed by a BSD-style license which is detailed in the
7+
* LICENSE file present in the project repository where this source code is maintained.
8+
*/
9+
10+
package org.readium.r2.lcp.persistence
11+
12+
import androidx.room.ColumnInfo
13+
import androidx.room.Entity
14+
import androidx.room.PrimaryKey
15+
16+
@Entity(tableName = License.TABLE_NAME)
17+
data class License(
18+
@PrimaryKey(autoGenerate = true)
19+
@ColumnInfo(name = ID)
20+
var id: Long? = null,
21+
@ColumnInfo(name = LICENSE_ID)
22+
var licenseId: String,
23+
@ColumnInfo(name = RIGHTPRINT)
24+
val rightPrint: Int?,
25+
@ColumnInfo(name = RIGHTCOPY)
26+
val rightCopy: Int?,
27+
@ColumnInfo(name = REGISTERED, defaultValue = "0")
28+
val registered: Boolean = false
29+
) {
30+
31+
companion object {
32+
33+
const val TABLE_NAME = "licenses"
34+
const val LICENSE_ID = "license_id"
35+
const val ID = "id"
36+
const val RIGHTPRINT = "right_print"
37+
const val RIGHTCOPY = "right_copy"
38+
const val REGISTERED = "registered"
39+
}
40+
}

0 commit comments

Comments
 (0)