Skip to content

Commit

Permalink
Migrate to new database API
Browse files Browse the repository at this point in the history
  • Loading branch information
bubelov committed May 16, 2024
1 parent e4bef9b commit 1d60da0
Show file tree
Hide file tree
Showing 18 changed files with 1,065 additions and 938 deletions.
20 changes: 5 additions & 15 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,6 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
excludes += "DebugProbesKt.bin"
}

// TODO remove bundled SQLite when Android bumps its deps
// > The JSON functions and operators are built into SQLite by default, as of SQLite version 3.38.0 (2022-02-22).
// https://www.sqlite.org/json1.html
//jniLibs.excludes += "/lib/armeabi-v7a/libsqlite3x.so"
//jniLibs.excludes += "lib/arm64-v8a/libsqlite3x.so"
jniLibs.excludes += "/lib/x86/**"
//jniLibs.excludes += "/lib/x86_64/**"
}

flavorDimensions += "store"
Expand Down Expand Up @@ -120,12 +112,11 @@ tasks.register("bundleData") {
dependencies {
implementation(libs.kotlinx.coroutines)

implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.androidx.preference.ktx)
implementation(libs.androidx.sqlite.ktx)
implementation(libs.androidx.sqlite.bundled)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.navigation.fragment)
implementation(libs.androidx.navigation.ui)
implementation(libs.androidx.preference)
implementation(libs.androidx.sqlite)
implementation(libs.androidx.work)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.room)

Expand All @@ -137,7 +128,6 @@ dependencies {
implementation(libs.osmdroid)
implementation(libs.jts)
implementation(libs.mpandroidchart)
implementation(libs.deprecatedsqlite)
implementation(libs.coil.core)
implementation(libs.coil.svg)

Expand Down
14 changes: 2 additions & 12 deletions app/src/main/kotlin/app/AppModule.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package app

import android.content.Context
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.sqlite.execSQL
import api.Api
import api.ApiImpl
import area.AreaModel
Expand All @@ -12,15 +9,13 @@ import area.AreasModel
import area.AreasRepo
import conf.ConfQueries
import conf.ConfRepo
import db.DB_FILE_NAME
import db.Database
import db.openDbConnection
import delivery.DeliveryModel
import element.ElementQueries
import element.ElementsRepo
import event.EventQueries
import event.EventsModel
import event.EventsRepo
import io.requery.android.database.sqlite.SQLiteOpenHelper
import issue.IssuesModel
import location.UserLocationRepository
import map.MapModel
Expand All @@ -41,12 +36,7 @@ import user.UsersModel
import user.UsersRepo

val appModule = module {
singleOf(::Database).bind(SQLiteOpenHelper::class)
single {
val context = get<Context>()
BundledSQLiteDriver().open(context.getDatabasePath(DB_FILE_NAME).absolutePath)
.apply { execSQL(Database.CREATE_AREA_TABLE) }
}
single { openDbConnection(get()) }

single { ApiImpl() }.bind(Api::class)

Expand Down
82 changes: 52 additions & 30 deletions app/src/main/kotlin/area/AreaQueries.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
package area

import androidx.sqlite.SQLiteConnection
import androidx.sqlite.execSQL
import androidx.sqlite.use
import db.getJsonObject
import db.getZonedDateTime
import java.time.ZonedDateTime

class AreaQueries(private val conn: SQLiteConnection) {

companion object {
const val CREATE_TABLE = """
CREATE TABLE area (
id INTEGER NOT NULL PRIMARY KEY,
tags TEXT NOT NULL,
updated_at TEXT NOT NULL
);
"""
}

fun insertOrReplace(areas: List<Area>) {
conn.execSQL("BEGIN IMMEDIATE TRANSACTION")

try {
areas.forEach { insertOrReplace(it) }
conn.execSQL("END TRANSACTION")
} catch (t: Throwable) {
conn.execSQL("ROLLBACK TRANSACTION")
}
}

fun insertOrReplace(area: Area) {
conn.prepare("INSERT OR REPLACE INTO area(id, tags, updated_at) VALUES(?1, ?2, ?3)").use {
it.bindLong(1, area.id)
Expand All @@ -18,10 +40,10 @@ class AreaQueries(private val conn: SQLiteConnection) {
}

fun selectById(id: Long): Area? {
conn.prepare("SELECT id, tags, updated_at FROM area WHERE id = ?1").use {
return conn.prepare("SELECT id, tags, updated_at FROM area WHERE id = ?1").use {
it.bindLong(1, id)

return if (it.step()) {
if (it.step()) {
Area(
id = it.getLong(0),
tags = it.getJsonObject(1),
Expand All @@ -34,32 +56,32 @@ class AreaQueries(private val conn: SQLiteConnection) {
}

fun selectByType(type: String): List<Area> {
val sql = """
return conn.prepare(
"""
SELECT id, tags, updated_at
FROM area
WHERE json_extract(tags, '$.type') = ?1
"""

val rows = mutableListOf<Area>()

conn.prepare(sql).use {
).use {
it.bindText(1, type)

while (it.step()) {
rows += Area(
id = it.getLong(0),
tags = it.getJsonObject(1),
updatedAt = it.getZonedDateTime(2),
)
buildList {
while (it.step()) {
add(
Area(
id = it.getLong(0),
tags = it.getJsonObject(1),
updatedAt = it.getZonedDateTime(2),
)
)
}
}
}

return rows
}

fun selectMaxUpdatedAt(): ZonedDateTime? {
conn.prepare("SELECT max(updated_at) FROM area").use {
return if (it.step()) {
return conn.prepare("SELECT max(updated_at) FROM area").use {
if (it.step()) {
it.getZonedDateTime(0)
} else {
null
Expand All @@ -68,7 +90,8 @@ class AreaQueries(private val conn: SQLiteConnection) {
}

fun selectMeetups(): List<Meetup> {
val sql = """
return conn.prepare(
"""
SELECT
json_extract(tags, '$.meetup_lat') AS lat,
json_extract(tags, '$.meetup_lon') AS lon,
Expand All @@ -78,20 +101,19 @@ class AreaQueries(private val conn: SQLiteConnection) {
lat IS NOT NULL
AND lon IS NOT NULL
"""

val rows = mutableListOf<Meetup>()

conn.prepare(sql).use {
while (it.step()) {
rows += Meetup(
lat = it.getDouble(0),
lon = it.getDouble(1),
areaId = it.getLong(2),
)
).use {
buildList {
while (it.step()) {
add(
Meetup(
lat = it.getDouble(0),
lon = it.getDouble(1),
areaId = it.getLong(2),
)
)
}
}
}

return rows
}

fun selectCount(): Long {
Expand Down
87 changes: 51 additions & 36 deletions app/src/main/kotlin/area/AreasRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,29 @@ class AreasRepo(
private val context: Context,
) {

suspend fun selectById(id: Long) = withContext(Dispatchers.IO) { queries.selectById(id) }
suspend fun selectById(id: Long): Area? {
return withContext(Dispatchers.IO) {
queries.selectById(id)
}
}

suspend fun selectByType(type: String) =
withContext(Dispatchers.IO) { queries.selectByType(type) }
suspend fun selectByType(type: String): List<Area> {
return withContext(Dispatchers.IO) {
queries.selectByType(type)
}
}

suspend fun selectMeetups() = withContext(Dispatchers.IO) { queries.selectMeetups() }
suspend fun selectMeetups(): List<Meetup> {
return withContext(Dispatchers.IO) {
queries.selectMeetups()
}
}

suspend fun selectCount() = withContext(Dispatchers.IO) { queries.selectCount() }
suspend fun selectCount(): Long {
return withContext(Dispatchers.IO) {
queries.selectCount()
}
}

suspend fun hasBundledAreas(): Boolean {
return withContext(Dispatchers.IO) {
Expand All @@ -32,33 +47,33 @@ class AreasRepo(
suspend fun fetchBundledAreas() {
withContext(Dispatchers.IO) {
context.assets.open("areas.json").use { bundledAreas ->
val areas = bundledAreas
.toAreasJson()
.filter { it.deletedAt == null }
.map { it.toArea() }

areas.forEach { queries.insertOrReplace(it) }
queries.insertOrReplace(
bundledAreas
.toAreasJson()
.filter { it.deletedAt == null }
.map { it.toArea() }
)
}
}
}

suspend fun sync(): SyncReport {
val startedAt = ZonedDateTime.now(ZoneOffset.UTC)
var newItems = 0L
var updatedItems = 0L
var deletedItems = 0L
var maxKnownUpdatedAt = withContext(Dispatchers.IO) { queries.selectMaxUpdatedAt() }

while (true) {
val delta = api.getAreas(maxKnownUpdatedAt, BATCH_SIZE)

if (delta.isEmpty()) {
break
} else {
maxKnownUpdatedAt = ZonedDateTime.parse(delta.maxBy { it.updatedAt }.updatedAt)
}
return withContext(Dispatchers.IO) {
val startedAt = ZonedDateTime.now(ZoneOffset.UTC)
var newItems = 0L
var updatedItems = 0L
var deletedItems = 0L
var maxKnownUpdatedAt = queries.selectMaxUpdatedAt()

while (true) {
val delta = api.getAreas(maxKnownUpdatedAt, BATCH_SIZE)

if (delta.isEmpty()) {
break
} else {
maxKnownUpdatedAt = ZonedDateTime.parse(delta.maxBy { it.updatedAt }.updatedAt)
}

withContext(Dispatchers.IO) {
delta.forEach {
val cached = queries.selectById(it.id)

Expand All @@ -79,19 +94,19 @@ class AreasRepo(
}
}
}
}

if (delta.size < BATCH_SIZE) {
break
if (delta.size < BATCH_SIZE) {
break
}
}
}

return SyncReport(
duration = Duration.between(startedAt, ZonedDateTime.now(ZoneOffset.UTC)),
newAreas = newItems,
updatedAreas = updatedItems,
deletedAreas = deletedItems,
)
SyncReport(
duration = Duration.between(startedAt, ZonedDateTime.now(ZoneOffset.UTC)),
newAreas = newItems,
updatedAreas = updatedItems,
deletedAreas = deletedItems,
)
}
}

data class SyncReport(
Expand Down
Loading

0 comments on commit 1d60da0

Please sign in to comment.