Skip to content
This repository has been archived by the owner on Jul 29, 2022. It is now read-only.

Commit

Permalink
Migrate from Anko to Room (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenzeck authored Sep 9, 2021
1 parent d19c2af commit 01470eb
Show file tree
Hide file tree
Showing 17 changed files with 318 additions and 293 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ All notable changes to this project will be documented in this file.

### Changed

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


## [2.0.0]
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {

repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.0'
Expand All @@ -20,7 +20,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
Expand Down
9 changes: 7 additions & 2 deletions r2-lcp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {
id 'com.android.library'
id 'kotlin-android'
id 'kotlin-parcelize'
id 'kotlin-kapt'
id 'maven-publish'
}

Expand Down Expand Up @@ -50,7 +51,7 @@ afterEvaluate {

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

if (findProject(':r2-shared')) {
implementation project(':r2-shared')
Expand All @@ -70,10 +71,14 @@ dependencies {
exclude module: 'support-v4'
}
implementation "joda-time:joda-time:2.10.10"
implementation "org.jetbrains.anko:anko-sqlite:0.10.8"
implementation "org.zeroturnaround:zt-zip:1.14"
implementation 'androidx.browser:browser:1.3.0'

final room_version = '2.4.0-alpha04'
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"

testImplementation "junit:junit:4.13.2"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"

Expand Down
13 changes: 8 additions & 5 deletions r2-lcp/src/main/java/org/readium/r2/lcp/LcpService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ package org.readium.r2.lcp
import android.content.Context
import kotlinx.coroutines.*
import org.readium.r2.lcp.auth.LcpDialogAuthentication
import org.readium.r2.lcp.persistence.Database
import org.readium.r2.lcp.persistence.LcpDatabase
import org.readium.r2.lcp.service.*
import org.readium.r2.shared.publication.ContentProtection
import org.readium.r2.shared.util.Try
Expand Down Expand Up @@ -107,12 +107,15 @@ interface LcpService {
if (!LcpClient.isAvailable())
return null

val db = Database(context)
val db = LcpDatabase.getDatabase(context).lcpDao()
val deviceRepository = DeviceRepository(db)
val passphraseRepository = PassphrasesRepository(db)
val licenseRepository = LicensesRepository(db)
val network = NetworkService()
val device = DeviceService(repository = db.licenses, network = network, context = context)
val device = DeviceService(repository = deviceRepository, network = network, context = context)
val crl = CRLService(network = network, context = context)
val passphrases = PassphrasesService(repository = db.transactions)
return LicensesService(licenses = db.licenses, crl = crl, device = device, network = network, passphrases = passphrases, context = context)
val passphrases = PassphrasesService(repository = passphraseRepository)
return LicensesService(licenses = licenseRepository, crl = crl, device = device, network = network, passphrases = passphrases, context = context)
}

@Deprecated("Use `LcpService()` instead", ReplaceWith("LcpService(context)"), level = DeprecationLevel.ERROR)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import android.net.Uri
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ListPopupWindow
import android.widget.PopupWindow
Expand All @@ -25,7 +26,6 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.anko.contentView
import org.readium.r2.lcp.LcpAuthenticating
import org.readium.r2.lcp.R
import org.readium.r2.lcp.license.model.components.Link
Expand Down Expand Up @@ -55,7 +55,7 @@ class LcpDialogAuthentication : LcpAuthenticating {
else null

private suspend fun askPassphrase(license: LcpAuthenticating.AuthenticatedLicense, reason: LcpAuthenticating.AuthenticationReason, sender: Any?): String? {
val hostView = (sender as? View) ?: (sender as? Activity)?.contentView ?: (sender as? Fragment)?.view
val hostView = (sender as? View) ?: (sender as? Activity)?.findViewById<ViewGroup>(android.R.id.content)?.getChildAt(0) ?: (sender as? Fragment)?.view
?: run {
Timber.e("No valid [sender] was passed to `LcpDialogAuthentication::retrievePassphrase()`. Make sure it is an Activity, a Fragment or a View.")
return null
Expand Down
121 changes: 70 additions & 51 deletions r2-lcp/src/main/java/org/readium/r2/lcp/persistence/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,64 +10,83 @@
package org.readium.r2.lcp.persistence

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import org.jetbrains.anko.db.INTEGER
import org.jetbrains.anko.db.ManagedSQLiteOpenHelper
import org.jetbrains.anko.db.TEXT
import org.jetbrains.anko.db.createTable
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase


// Access property for Context
internal val Context.database: LcpDatabaseOpenHelper
get() = LcpDatabaseOpenHelper.getInstance(applicationContext)
@Database(
entities = [Passphrase::class, License::class],
version = 2,
exportSchema = false
)
internal abstract class LcpDatabase : RoomDatabase() {

internal val Context.appContext: Context
get() = applicationContext
abstract fun lcpDao(): LcpDao


internal class Database(context: Context) {

val shared: LcpDatabaseOpenHelper = LcpDatabaseOpenHelper(context)
var licenses: Licenses
var transactions: Transactions

init {
licenses = Licenses(shared)
transactions = Transactions(shared)
}

}

internal class LcpDatabaseOpenHelper(ctx: Context) : ManagedSQLiteOpenHelper(ctx, "lcpdatabase", null, 1) {
companion object {
private var instance: LcpDatabaseOpenHelper? = null
@Volatile
private var INSTANCE: LcpDatabase? = null

@Synchronized
fun getInstance(ctx: Context): LcpDatabaseOpenHelper {
if (instance == null) {
instance = LcpDatabaseOpenHelper(ctx.applicationContext)
fun getDatabase(context: Context): LcpDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE passphrases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
license_id TEXT,
provider TEXT,
user_id TEXT,
passphrase TEXT NOT NULL
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO passphrases (license_id, provider, user_id, passphrase)
SELECT id, origin, userId, passphrase FROM Transactions
""".trimIndent()
)
database.execSQL("DROP TABLE Transactions")


database.execSQL(
"""
CREATE TABLE new_Licenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
license_id TEXT NOT NULL,
right_print INTEGER,
right_copy INTEGER,
registered INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO new_Licenses (license_id, right_print, right_copy, registered)
SELECT id, printsLeft, copiesLeft, registered FROM Licenses
""".trimIndent()
)
database.execSQL("DROP TABLE Licenses")
database.execSQL("ALTER TABLE new_Licenses RENAME TO licenses")
}
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
LcpDatabase::class.java,
"lcpdatabase"
).allowMainThreadQueries().addMigrations(MIGRATION_1_2).build()
INSTANCE = instance
return instance
}
return instance!!
}
}

override fun onCreate(db: SQLiteDatabase) {

db.createTable(LicensesTable.NAME, true,
LicensesTable.ID to TEXT,
LicensesTable.PRINTSLEFT to INTEGER,
LicensesTable.COPIESLEFT to INTEGER,
LicensesTable.REGISTERED to INTEGER)

db.createTable(TransactionsTable.NAME, true,
TransactionsTable.ID to TEXT,
TransactionsTable.ORIGIN to TEXT,
TransactionsTable.USERID to TEXT,
TransactionsTable.PASSPHRASE to TEXT)

}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
// Here you can upgrade tables, as usual
}
}
50 changes: 50 additions & 0 deletions r2-lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.readium.r2.lcp.persistence

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query

@Dao
interface LcpDao {

/**
* Retrieve passphrase
* @return Passphrase
*/
@Query("SELECT ${Passphrase.PASSPHRASE} FROM ${Passphrase.TABLE_NAME} WHERE ${Passphrase.PROVIDER} = :licenseId")
suspend fun passphrase(licenseId: String): String?

@Query("SELECT ${Passphrase.PASSPHRASE} FROM ${Passphrase.TABLE_NAME} WHERE ${Passphrase.USERID} = :userId")
suspend fun passphrases(userId: String): List<String>

@Query("SELECT ${Passphrase.PASSPHRASE} FROM ${Passphrase.TABLE_NAME}")
suspend fun allPassphrases(): List<String>

@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addPassphrase(passphrase: Passphrase)

@Query("SELECT ${License.LICENSE_ID} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId")
suspend fun exists(licenseId: String): String?

@Query("SELECT ${License.REGISTERED} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId")
suspend fun isDeviceRegistered(licenseId: String): Boolean

@Query("UPDATE ${License.TABLE_NAME} SET ${License.REGISTERED} = 1 WHERE ${License.LICENSE_ID} = :licenseId")
suspend fun registerDevice(licenseId: String)

@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addLicense(license: License)

@Query("SELECT ${License.RIGHTCOPY} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId")
fun getCopiesLeft(licenseId: String): Int?

@Query("UPDATE ${License.TABLE_NAME} SET ${License.RIGHTCOPY} = :quantity WHERE ${License.LICENSE_ID} = :licenseId")
fun setCopiesLeft(quantity: Int, licenseId: String)

@Query("SELECT ${License.RIGHTPRINT} FROM ${License.TABLE_NAME} WHERE ${License.LICENSE_ID} = :licenseId")
fun getPrintsLeft(licenseId: String): Int?

@Query("UPDATE ${License.TABLE_NAME} SET ${License.RIGHTPRINT} = :quantity WHERE ${License.LICENSE_ID} = :licenseId")
fun setPrintsLeft(quantity: Int, licenseId: String)
}
40 changes: 40 additions & 0 deletions r2-lcp/src/main/java/org/readium/r2/lcp/persistence/License.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Module: r2-lcp-kotlin
* Developers: Aferdita Muriqi
*
* Copyright (c) 2019. Readium Foundation. All rights reserved.
* Use of this source code is governed by a BSD-style license which is detailed in the
* LICENSE file present in the project repository where this source code is maintained.
*/

package org.readium.r2.lcp.persistence

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = License.TABLE_NAME)
data class License(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = ID)
var id: Long? = null,
@ColumnInfo(name = LICENSE_ID)
var licenseId: String,
@ColumnInfo(name = RIGHTPRINT)
val rightPrint: Int?,
@ColumnInfo(name = RIGHTCOPY)
val rightCopy: Int?,
@ColumnInfo(name = REGISTERED, defaultValue = "0")
val registered: Boolean = false
) {

companion object {

const val TABLE_NAME = "licenses"
const val LICENSE_ID = "license_id"
const val ID = "id"
const val RIGHTPRINT = "right_print"
const val RIGHTCOPY = "right_copy"
const val REGISTERED = "registered"
}
}
Loading

0 comments on commit 01470eb

Please sign in to comment.