Skip to content

Commit

Permalink
Migrate to new sqlcipher package
Browse files Browse the repository at this point in the history
  • Loading branch information
raphaelm committed Mar 21, 2024
1 parent 0c3d696 commit 51aa0ec
Show file tree
Hide file tree
Showing 7 changed files with 558 additions and 2 deletions.
3 changes: 2 additions & 1 deletion pretixscan/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.preference:preference-ktx:1.2.1"
implementation 'androidx.sqlite:sqlite:2.2.0' // 2.4.0 requires compileSDK 34

implementation 'com.louiscad.splitties:splitties-toast:3.0.0'
implementation 'com.github.traex.rippleeffect:library:1.3'
Expand All @@ -139,7 +140,7 @@ dependencies {
implementation 'io.requery:requery:1.6.0'
implementation 'io.requery:requery-android:1.6.0'
implementation 'io.requery:requery-kotlin:1.6.0'
implementation 'net.zetetic:android-database-sqlcipher:3.5.9'
implementation 'net.zetetic:sqlcipher-android:4.5.5'
implementation 'net.sourceforge.streamsupport:streamsupport-cfuture:1.7.3'
implementation "com.fasterxml.jackson.core:jackson-databind:2.13.0"
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import eu.pretix.libpretixsync.check.TicketCheckProvider
import eu.pretix.libpretixsync.db.Migrations
import eu.pretix.libpretixui.android.covid.DGC
import eu.pretix.pretixscan.droid.connectivity.ConnectivityHelper
import eu.pretix.pretixscan.droid.db.SqlCipherDatabaseSource
import eu.pretix.pretixscan.utils.KeystoreHelper
import io.requery.BlockingEntityStore
import io.requery.Persistable
import io.requery.android.sqlcipher.SqlCipherDatabaseSource
import io.requery.android.sqlite.DatabaseSource
import io.requery.sql.EntityDataStore
import java.util.concurrent.locks.ReentrantLock
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Based on code
* Copyright 2019 requery.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package eu.pretix.pretixscan.droid.db


import android.database.sqlite.SQLiteConstraintException
import android.database.sqlite.SQLiteException
import io.requery.android.sqlite.BaseConnection
import net.zetetic.database.sqlcipher.SQLiteDatabase
import java.sql.DatabaseMetaData
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.SQLException
import java.sql.SQLFeatureNotSupportedException
import java.sql.SQLIntegrityConstraintViolationException
import java.sql.Statement

/**
* [java.sql.Connection] implementation using SQLCipher SQLite Android API.
*
* @author Nikhil Purushe
*/
internal class SqlCipherConnection(val database: SQLiteDatabase) : BaseConnection() {
private val metaData: SqlCipherMetaData
private var enteredTransaction: Boolean = false

init {
autoCommit = true
metaData = SqlCipherMetaData(this)
}

override fun ensureTransaction() {
if (!autoCommit) {
if (!database.inTransaction()) {
database.beginTransaction()
enteredTransaction = true
}
}
}

@Throws(SQLException::class)
override fun execSQL(sql: String) {
try {
database.execSQL(sql)
} catch (e: SQLiteException) {
throwSQLException(e)
}

}

@Throws(SQLException::class)
override fun commit() {
if (autoCommit) {
throw SQLException("commit called while in autoCommit mode")
}
if (database.inTransaction() && enteredTransaction) {
try {
database.setTransactionSuccessful()
} catch (e: IllegalStateException) {
throw SQLException(e)
} finally {
database.endTransaction()
enteredTransaction = false
}
}
}

@Throws(SQLException::class)
override fun createStatement(): Statement {
return SqlCipherStatement(this)
}

@Throws(SQLException::class)
override fun createStatement(resultSetType: Int, resultSetConcurrency: Int): Statement {
return createStatement(resultSetType,
resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT)
}

@Throws(SQLException::class)
override fun createStatement(resultSetType: Int, resultSetConcurrency: Int,
resultSetHoldability: Int): Statement {
if (resultSetConcurrency == ResultSet.CONCUR_UPDATABLE) {
throw SQLFeatureNotSupportedException("CONCUR_UPDATABLE not supported")
}
return SqlCipherStatement(this)
}

@Throws(SQLException::class)
override fun getMetaData(): DatabaseMetaData {
return metaData
}

@Throws(SQLException::class)
override fun isClosed(): Boolean {
return !database.isOpen()
}

@Throws(SQLException::class)
override fun isReadOnly(): Boolean {
return database.isReadOnly()
}

@Throws(SQLException::class)
override fun prepareStatement(sql: String, autoGeneratedKeys: Int): PreparedStatement {
return SqlCipherPreparedStatement(this, sql, autoGeneratedKeys)
}

@Throws(SQLException::class)
override fun prepareStatement(sql: String,
resultSetType: Int,
resultSetConcurrency: Int,
resultSetHoldability: Int): PreparedStatement {
return SqlCipherPreparedStatement(this, sql, Statement.NO_GENERATED_KEYS)
}

@Throws(SQLException::class)
override fun prepareStatement(sql: String, columnNames: Array<String>): PreparedStatement {
return SqlCipherPreparedStatement(this, sql, Statement.RETURN_GENERATED_KEYS)
}

@Throws(SQLException::class)
override fun rollback() {
if (autoCommit) {
throw SQLException("commit called while in autoCommit mode")
}
database.endTransaction()
}

companion object {
@Throws(SQLException::class)
fun throwSQLException(exception: SQLiteException) {
if (exception is SQLiteConstraintException) {
throw SQLIntegrityConstraintViolationException(exception)
}
throw SQLException(exception)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Based on code
* Copyright 2019 requery.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package eu.pretix.pretixscan.droid.db

import android.content.Context
import io.requery.android.DefaultMapping
import io.requery.android.LoggingListener
import io.requery.android.sqlite.DatabaseProvider
import io.requery.android.sqlite.SchemaUpdater
import io.requery.meta.EntityModel
import io.requery.sql.Configuration
import io.requery.sql.ConfigurationBuilder
import io.requery.sql.Mapping
import io.requery.sql.Platform
import io.requery.sql.SchemaModifier
import io.requery.sql.TableCreationMode
import io.requery.sql.platform.SQLite
import io.requery.util.function.Function
import net.zetetic.database.sqlcipher.SQLiteDatabase
import net.zetetic.database.sqlcipher.SQLiteOpenHelper
import java.sql.Connection

open class SqlCipherDatabaseSource(context: Context,
private val model: EntityModel,
name: String,
private val password: String,
version: Int)
: SQLiteOpenHelper(context, name, password, null, version, 0, null, null, true), DatabaseProvider<SQLiteDatabase> {

private val platform: Platform
private val mapping: Mapping
private var db: SQLiteDatabase? = null
private var _configuration: Configuration? = null
private var loggingEnabled: Boolean = false
private var mode: TableCreationMode? = null

init {
this.platform = SQLite()
this.mapping = onCreateMapping(platform)
this.mode = TableCreationMode.CREATE_NOT_EXISTS
System.loadLibrary("sqlcipher");
}

override fun setLoggingEnabled(enable: Boolean) {
this.loggingEnabled = enable
}

override fun setTableCreationMode(mode: TableCreationMode) {
this.mode = mode
}

protected fun onCreateMapping(platform: Platform): Mapping {
return DefaultMapping(platform)
}

protected fun onConfigure(builder: ConfigurationBuilder) {
if (loggingEnabled) {
val loggingListener = LoggingListener()
builder.addStatementListener(loggingListener)
}
}

private fun getConnection(db: SQLiteDatabase): SqlCipherConnection {
synchronized(this) {
return SqlCipherConnection(db)
}
}

override fun getConfiguration(): Configuration {
if (_configuration == null) {
val builder = ConfigurationBuilder(this, model)
.setMapping(mapping)
.setPlatform(platform)
.setBatchUpdateSize(1000)
onConfigure(builder)
_configuration = builder.build()
}
return _configuration!!
}

override fun onCreate(db: SQLiteDatabase) {
this.db = db
SchemaModifier(configuration).createTables(TableCreationMode.CREATE)
}

override fun onConfigure(db: SQLiteDatabase) {}

override fun onOpen(db: SQLiteDatabase?) {
super.onOpen(db)
if (!db!!.isReadOnly) {
db.execSQL("PRAGMA foreign_keys = ON")
}
}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
this.db = db
val updater = SchemaUpdater(configuration,
Function { s -> db.rawQuery(s, null) }, mode)
updater.update()
}

override fun getConnection(): Connection {
synchronized(this) {
if (db == null) {
db = getWritableDatabase()
}
return getConnection(db!!)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2018 requery.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package eu.pretix.pretixscan.droid.db

import android.database.Cursor
import android.database.sqlite.SQLiteException
import io.requery.android.sqlite.BaseConnection
import io.requery.android.sqlite.SqliteMetaData
import io.requery.util.function.Function
import net.zetetic.database.sqlcipher.SQLiteDatabase
import java.io.Closeable

import java.sql.SQLException

internal class SqlCipherMetaData(connection: BaseConnection) : SqliteMetaData(connection) {

@Throws(SQLException::class)
override fun <R> queryMemory(function: Function<Cursor, R>, query: String): R {
try {
val database = SQLiteDatabase.openOrCreateDatabase(":memory:", "", null, null)
val cursor = database.rawQuery(query, null)
return function.apply(closeWithCursor(Closeable { database.close() }, cursor))
} catch (e: SQLiteException) {
throw SQLException(e)
}

}
}
Loading

0 comments on commit 51aa0ec

Please sign in to comment.