diff --git a/build.gradle.kts b/build.gradle.kts index 9434477..0687328 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,6 @@ plugins { alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinJvm) apply false alias(libs.plugins.kotlinMultiplatform) apply false - alias(libs.plugins.sqldelight) apply false alias(libs.plugins.wire) apply false } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 3dd2ee6..99f7156 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -4,6 +4,12 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.ksp) + alias(libs.plugins.room) +} + +room { + schemaDirectory("$projectDir/schemas") } kotlin { @@ -40,9 +46,18 @@ kotlin { implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.protobuf) + implementation(libs.room.runtime) + implementation(libs.sqlite.bundled) } iosMain.dependencies { implementation(libs.ktor.client.darwin) } } } + +dependencies { + add("kspAndroid", libs.room.compiler) + add("kspIosArm64", libs.room.compiler) + add("kspIosSimulatorArm64", libs.room.compiler) + add("kspJvm", libs.room.compiler) +} diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index ecdba19..6f78b5c 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -25,40 +25,8 @@ kotlin { jvm() sourceSets { - val clientMain by creating { - dependsOn(commonMain.get()) - } - - androidMain.get().dependsOn(clientMain) - iosArm64Main.get().dependsOn(clientMain) - iosSimulatorArm64Main.get().dependsOn(clientMain) - commonMain.dependencies { - implementation(libs.koin.core) implementation(projects.core) - api(projects.core.stoptime) - } - - androidMain.dependencies { - implementation(libs.koin.compose) - implementation(libs.ktor.client.okhttp) - } - commonMain.dependencies { - implementation(libs.okio) - implementation(libs.koin.core) - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.contentnegotiation) - implementation(libs.ktor.serialization.kotlinx.json) - implementation(libs.kotlinx.coroutines.core) - implementation(libs.kotlinx.datetime) - implementation(libs.kotlinx.serialization.json) - implementation(libs.kotlinx.serialization.protobuf) - - implementation(projects.core) - implementation(projects.core.sqld) - } - iosMain.dependencies { - implementation(libs.ktor.client.darwin) } } } diff --git a/core/stoptime/build.gradle.kts b/core/data/client/build.gradle.kts similarity index 71% rename from core/stoptime/build.gradle.kts rename to core/data/client/build.gradle.kts index 44cf072..e9848f3 100644 --- a/core/stoptime/build.gradle.kts +++ b/core/data/client/build.gradle.kts @@ -9,7 +9,7 @@ plugins { kotlin { android { - namespace = "moe.lava.banksia.core.stoptime" + namespace = "moe.lava.banksia.core.data.client" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { @@ -19,7 +19,6 @@ kotlin { compilerOptions { freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - freeCompilerArgs.add("-Xexpect-actual-classes") } iosArm64() @@ -28,18 +27,13 @@ kotlin { jvm() sourceSets { - val clientMain by creating { - dependsOn(commonMain.get()) - } - - androidMain.get().dependsOn(clientMain) - iosArm64Main.get().dependsOn(clientMain) - iosSimulatorArm64Main.get().dependsOn(clientMain) - androidMain.dependencies { + implementation(libs.koin.compose) implementation(libs.ktor.client.okhttp) } commonMain.dependencies { + api(projects.core.data) + implementation(libs.okio) implementation(libs.koin.core) implementation(libs.ktor.client.core) @@ -51,14 +45,10 @@ kotlin { implementation(libs.kotlinx.serialization.protobuf) implementation(projects.core) - implementation(projects.core.sqld) + implementation(projects.core.room) } iosMain.dependencies { implementation(libs.ktor.client.darwin) } - jvmMain.dependencies { - implementation(libs.koin.ktor) - implementation(libs.ktor.server.core) - } } } diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt similarity index 78% rename from core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt index 104c6bc..01e961c 100644 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt @@ -10,19 +10,26 @@ import kotlinx.serialization.json.Json import moe.lava.banksia.core.Constants import moe.lava.banksia.core.data.repositories.ClientRouteRepository import moe.lava.banksia.core.data.repositories.ClientStopRepository +import moe.lava.banksia.core.data.repositories.ClientStopTimeRepository import moe.lava.banksia.core.data.repositories.RouteRepository import moe.lava.banksia.core.data.repositories.StopRepository +import moe.lava.banksia.core.data.repositories.StopTimeRepository import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource +import moe.lava.banksia.core.room.roomDiModule import moe.lava.banksia.core.util.log import moe.lava.banksia.data.ptv.PtvService import org.koin.core.module.dsl.singleOf import org.koin.dsl.bind import org.koin.dsl.module -actual val platformModule = module { +val clientDataDiModule = module { + includes(roomDiModule) + // HTTP Clients singleOf(::PtvService) single { @@ -49,8 +56,11 @@ actual val platformModule = module { singleOf(::RouteRemoteDataSource) singleOf(::StopLocalDataSource) singleOf(::StopRemoteDataSource) + singleOf(::StopTimeLocalDataSource) + singleOf(::StopTimeRemoteDataSource) // Repositories singleOf(::ClientRouteRepository) bind RouteRepository::class singleOf(::ClientStopRepository) bind StopRepository::class + singleOf(::ClientStopTimeRepository) bind StopTimeRepository::class } diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt similarity index 75% rename from core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt index f46caac..2644785 100644 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.sync.withLock import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.sqld.mappers.asModel internal class ClientRouteRepository internal constructor( private val local: RouteLocalDataSource, @@ -23,14 +22,14 @@ internal class ClientRouteRepository internal constructor( } } - private val tripRouteMap = mutableMapOf() + private val tripRouteMap = mutableMapOf() override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) } - override suspend fun getByPattern(patternId: Long) = mutex.withLock { - tripRouteMap[patternId] - ?: remote.getByPattern(patternId).also { + override suspend fun getByTrip(tripId: String) = mutex.withLock { + tripRouteMap[tripId] + ?: remote.getByTrip(tripId).also { local.save(it) - tripRouteMap[patternId] = it + tripRouteMap[tripId] = it } } } diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt similarity index 94% rename from core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt index 0aee84e..a5fd300 100644 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt @@ -4,7 +4,6 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource -import moe.lava.banksia.core.sqld.mappers.asModel internal class ClientStopRepository internal constructor( private val local: StopLocalDataSource, diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt new file mode 100644 index 0000000..aea3159 --- /dev/null +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt @@ -0,0 +1,16 @@ +package moe.lava.banksia.core.data.repositories + +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource +import moe.lava.banksia.core.model.StopTimeDated + +internal class ClientStopTimeRepository internal constructor( + private val local: StopTimeLocalDataSource, + private val remote: StopTimeRemoteDataSource, +) : StopTimeRepository { + override suspend fun getForStop(id: String): List { + return local + .getAtStop(id) + .ifEmpty { remote.getAtStop(id) } + } +} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt new file mode 100644 index 0000000..e319c80 --- /dev/null +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt @@ -0,0 +1,12 @@ +package moe.lava.banksia.core.data.sources.route + +import moe.lava.banksia.core.model.Route +import moe.lava.banksia.core.room.dao.RouteDao +import moe.lava.banksia.core.room.entity.asEntity + +internal class RouteLocalDataSource(private val dao: RouteDao) { + suspend fun get(id: String) = dao.get(id) + suspend fun getAll() = dao.getAll() + suspend fun getByTrip(tripId: String) = dao.getByTrip(tripId) + suspend fun save(vararg routes: Route) = dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) +} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt similarity index 78% rename from core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt index 15088fb..b37bff1 100644 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt @@ -7,6 +7,6 @@ import moe.lava.banksia.core.model.Route internal class RouteRemoteDataSource(val client: HttpClient) { suspend fun get(id: String) = client.get("routes/${id}").body() - suspend fun getByPattern(patternId: Long) = client.get("routes/by_pattern/${patternId}").body() + suspend fun getByTrip(tripId: String) = client.get("routes/by_trip/${tripId}").body() suspend fun getAll() = client.get("routes").body>() } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt new file mode 100644 index 0000000..8e0d8ab --- /dev/null +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt @@ -0,0 +1,12 @@ +package moe.lava.banksia.core.data.sources.stop + +import moe.lava.banksia.core.model.Stop +import moe.lava.banksia.core.room.dao.RouteDao +import moe.lava.banksia.core.room.dao.StopDao +import moe.lava.banksia.core.room.entity.asEntity + +internal class StopLocalDataSource(private val dao: StopDao, private val routeDao: RouteDao) { + suspend fun get(id: String) = dao.get(id) + suspend fun getByRoute(id: String) = routeDao.stops(id) + suspend fun save(vararg stops: Stop) = dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) +} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt similarity index 100% rename from core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt new file mode 100644 index 0000000..c5ce4e7 --- /dev/null +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt @@ -0,0 +1,28 @@ +package moe.lava.banksia.core.data.sources.stoptime + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.core.model.StopTimeDated +import moe.lava.banksia.core.model.atDate +import moe.lava.banksia.core.room.dao.StopTimeDao +import moe.lava.banksia.core.util.serialise +import kotlin.time.Clock + +internal class StopTimeLocalDataSource( + private val stopTimeDao: StopTimeDao, +) { + suspend fun getAtStop( + stopId: String, + date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), + ): List { + return stopTimeDao + .getForStopDated( + stopId, + listOf(date.dayOfWeek).serialise(), + date.toEpochDays().toInt(), + ) + .map { it.asModel().atDate(date) } + .sortedBy { it.departureTime } + } +} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt new file mode 100644 index 0000000..0633a18 --- /dev/null +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt @@ -0,0 +1,36 @@ +package moe.lava.banksia.core.data.sources.stoptime + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.request.parameter +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.core.model.StopTimeDated +import kotlin.time.Clock + +internal class StopTimeRemoteDataSource( + private val client: HttpClient, +) { + suspend fun getAtStop( + stopId: String, + date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()), + ): List { + return client.get("stoptimes/by_stop/${stopId}") { + parameter("date", date) + }.body>() + } + + /*suspend fun get( + stop: String? = null, + trip: String? = null, + day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek, + ): List { + return client.get("stoptimes") { + stop?.let { parameter("stop", it) } + trip?.let { parameter("trip", it) } + day?.let { parameter("day", it) } + }.body>() + }*/ +} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt new file mode 100644 index 0000000..d1067d8 --- /dev/null +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt @@ -0,0 +1,18 @@ +package moe.lava.banksia.core.data.sources.trip + +import io.ktor.client.HttpClient +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.core.model.Trip +import kotlin.time.Clock + +internal class TripRemoteDataSource( + private val client: HttpClient, +) { + suspend fun get( + day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek, + ): List { + return listOf() + } +} diff --git a/core/data/server/build.gradle.kts b/core/data/server/build.gradle.kts new file mode 100644 index 0000000..eaa309b --- /dev/null +++ b/core/data/server/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.kotlinSerialization) +} + +kotlin { + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + } +} + +dependencies { + implementation(libs.okio) + implementation(libs.koin.core) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + + api(projects.core.data) + implementation(projects.core) + implementation(projects.core.room) +} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt deleted file mode 100644 index 8286b1f..0000000 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt +++ /dev/null @@ -1,23 +0,0 @@ -package moe.lava.banksia.core.data.sources.route - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext -import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.sqld.RouteQueries -import moe.lava.banksia.core.sqld.mappers.asDb - -internal class RouteLocalDataSource(private val queries: RouteQueries) { - suspend fun get(id: String) = withContext(Dispatchers.IO) { queries.get(id).executeAsOneOrNull() } - suspend fun getAll() = withContext(Dispatchers.IO) { queries.getAll().executeAsList() } -// suspend fun getByTrip(tripId: String) = dao.getByTrip(tripId) - suspend fun save(vararg routes: Route) { - withContext(Dispatchers.IO) { - queries.transaction { - routes.forEach { - queries.insert(it.asDb()) - } - } - } - } -} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt deleted file mode 100644 index 524d123..0000000 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt +++ /dev/null @@ -1,22 +0,0 @@ -package moe.lava.banksia.core.data.sources.stop - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext -import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.sqld.StopQueries -import moe.lava.banksia.core.sqld.mappers.asDb - -internal class StopLocalDataSource(private val queries: StopQueries) { - suspend fun get(id: String) = withContext(Dispatchers.IO) { queries.get(id).executeAsOneOrNull() } - suspend fun getByRoute(id: String) = withContext(Dispatchers.IO) { queries.getByRoute(id).executeAsList() } - suspend fun save(vararg stops: Stop) { - withContext(Dispatchers.IO) { - queries.transaction { - stops.forEach { - queries.insert(it.asDb()) - } - } - } - } -} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt deleted file mode 100644 index eea6a0e..0000000 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt +++ /dev/null @@ -1,13 +0,0 @@ -package moe.lava.banksia.core.data - -import moe.lava.banksia.core.sqld.sqldDiModule -import org.koin.core.module.Module -import org.koin.dsl.module - -internal expect val platformModule: Module - -val dataDiModule = module { - includes(platformModule) - includes(sqldDiModule) - includes(stopTimeDataDiModule) -} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt index ef3d6f1..fb302a5 100644 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt @@ -4,6 +4,6 @@ import moe.lava.banksia.core.model.Route interface RouteRepository { suspend fun get(id: String): Route? - suspend fun getByPattern(patternId: Long): Route? + suspend fun getByTrip(tripId: String): Route? suspend fun getAll(): List } diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt new file mode 100644 index 0000000..87d01ac --- /dev/null +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt @@ -0,0 +1,7 @@ +package moe.lava.banksia.core.data.repositories + +import moe.lava.banksia.core.model.StopTimeDated + +interface StopTimeRepository { + suspend fun getForStop(id: String): List +} diff --git a/core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt b/core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt deleted file mode 100644 index 78a44d1..0000000 --- a/core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt +++ /dev/null @@ -1,7 +0,0 @@ -package moe.lava.banksia.core.data - -import org.koin.dsl.module - -internal actual val platformModule = module { - -} diff --git a/core/sqld/build.gradle.kts b/core/room/build.gradle.kts similarity index 55% rename from core/sqld/build.gradle.kts rename to core/room/build.gradle.kts index 472a908..31a7393 100644 --- a/core/sqld/build.gradle.kts +++ b/core/room/build.gradle.kts @@ -4,12 +4,17 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) alias(libs.plugins.androidMultiplatformLibrary) - alias(libs.plugins.sqldelight) + alias(libs.plugins.ksp) + alias(libs.plugins.room) +} + +room { + schemaDirectory("$projectDir/schemas") } kotlin { android { - namespace = "moe.lava.banksia.core.sqld" + namespace = "moe.lava.banksia.core.room" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { @@ -17,37 +22,33 @@ kotlin { } } + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + } + iosArm64() iosSimulatorArm64() jvm() sourceSets { - androidMain.dependencies { - implementation(libs.sqldelight.driver.android) - } commonMain.dependencies { implementation(libs.okio) implementation(libs.koin.core) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.room.runtime) + implementation(libs.sqlite.bundled) implementation(projects.core) } - nativeMain.dependencies { - implementation(libs.sqldelight.driver.native) - } - jvmMain.dependencies { - implementation(libs.sqldelight.driver.jvm) - } } } -sqldelight { - databases { - register("BanksiaDatabase") { - packageName.set("moe.lava.banksia.core.sqld") - schemaOutputDirectory.set(file("src/commonMain/sqldelight/schema")) - } - } +dependencies { + add("kspAndroid", libs.room.compiler) + add("kspIosArm64", libs.room.compiler) + add("kspIosSimulatorArm64", libs.room.compiler) + add("kspJvm", libs.room.compiler) } diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/1.json b/core/room/schemas/moe.lava.banksia.core.room.Database/1.json new file mode 100644 index 0000000..037062e --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/1.json @@ -0,0 +1,72 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "e536f5a9b1408377bcc449195169648c", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e536f5a9b1408377bcc449195169648c')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/10.json b/core/room/schemas/moe.lava.banksia.core.room.Database/10.json new file mode 100644 index 0000000..751e946 --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/10.json @@ -0,0 +1,477 @@ +{ + "formatVersion": 1, + "database": { + "version": 10, + "identityHash": "5b90bc800bfae6d22124ea0a6a906ca7", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "ServiceException", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))", + "fields": [ + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "serviceId", + "date" + ] + }, + "indices": [ + { + "name": "index_ServiceException_serviceId", + "unique": false, + "columnNames": [ + "serviceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)" + }, + { + "name": "index_ServiceException_type", + "unique": false, + "columnNames": [ + "type" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Service", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serviceId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5b90bc800bfae6d22124ea0a6a906ca7')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/11.json b/core/room/schemas/moe.lava.banksia.core.room.Database/11.json new file mode 100644 index 0000000..6fc2976 --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/11.json @@ -0,0 +1,498 @@ +{ + "formatVersion": 1, + "database": { + "version": 11, + "identityHash": "c4be3d0c2a25f8c5c33132646a070d0e", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "ServiceException", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))", + "fields": [ + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "serviceId", + "date" + ] + }, + "indices": [ + { + "name": "index_ServiceException_serviceId", + "unique": false, + "columnNames": [ + "serviceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)" + }, + { + "name": "index_ServiceException_type", + "unique": false, + "columnNames": [ + "type" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT" + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ], + "foreignKeys": [ + { + "table": "Stop", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "parent" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_serviceId", + "unique": false, + "columnNames": [ + "serviceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `${TABLE_NAME}` (`serviceId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Service", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serviceId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c4be3d0c2a25f8c5c33132646a070d0e')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/2.json b/core/room/schemas/moe.lava.banksia.core.room.Database/2.json new file mode 100644 index 0000000..04a14e3 --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/2.json @@ -0,0 +1,315 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "83ece554400bb035c267dc2414c23293", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '83ece554400bb035c267dc2414c23293')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/3.json b/core/room/schemas/moe.lava.banksia.core.room.Database/3.json new file mode 100644 index 0000000..e769926 --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/3.json @@ -0,0 +1,339 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "5a7252ab3bcae4d0d0950024b19ba002", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5a7252ab3bcae4d0d0950024b19ba002')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/4.json b/core/room/schemas/moe.lava.banksia.core.room.Database/4.json new file mode 100644 index 0000000..783b3ee --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/4.json @@ -0,0 +1,368 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "4426fd2ccc826d9d9d9021546b105850", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": true, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": true, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/5.json b/core/room/schemas/moe.lava.banksia.core.room.Database/5.json new file mode 100644 index 0000000..c4a786d --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/5.json @@ -0,0 +1,368 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "4426fd2ccc826d9d9d9021546b105850", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": true, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": true, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/6.json b/core/room/schemas/moe.lava.banksia.core.room.Database/6.json new file mode 100644 index 0000000..5ab26dc --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/6.json @@ -0,0 +1,368 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "identityHash": "5f52de4cc0ddbcf02a0d8be4cf4d4cfd", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5f52de4cc0ddbcf02a0d8be4cf4d4cfd')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/7.json b/core/room/schemas/moe.lava.banksia.core.room.Database/7.json new file mode 100644 index 0000000..d4c62b2 --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/7.json @@ -0,0 +1,415 @@ +{ + "formatVersion": 1, + "database": { + "version": 7, + "identityHash": "15c94df0a62438ff28d451c074c94c59", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '15c94df0a62438ff28d451c074c94c59')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/8.json b/core/room/schemas/moe.lava.banksia.core.room.Database/8.json new file mode 100644 index 0000000..9240dd5 --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/8.json @@ -0,0 +1,426 @@ +{ + "formatVersion": 1, + "database": { + "version": 8, + "identityHash": "6e0f07bf1af88b2e37b5ad7c38a3fb2a", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Service", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serviceId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e0f07bf1af88b2e37b5ad7c38a3fb2a')" + ] + } +} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/9.json b/core/room/schemas/moe.lava.banksia.core.room.Database/9.json new file mode 100644 index 0000000..2359dbd --- /dev/null +++ b/core/room/schemas/moe.lava.banksia.core.room.Database/9.json @@ -0,0 +1,426 @@ +{ + "formatVersion": 1, + "database": { + "version": 9, + "identityHash": "6e0f07bf1af88b2e37b5ad7c38a3fb2a", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Service", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serviceId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e0f07bf1af88b2e37b5ad7c38a3fb2a')" + ] + } +} \ No newline at end of file diff --git a/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt b/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt new file mode 100644 index 0000000..999ee4b --- /dev/null +++ b/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt @@ -0,0 +1,22 @@ +package moe.lava.banksia.core.room + +import android.content.Context +import androidx.room.Room +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import org.koin.core.component.inject + +actual class DatabaseManager actual constructor() : KoinComponent { + private val ctx by inject() + + actual val database by lazy { + val ctx = get().applicationContext + val dbFile = ctx.getDatabasePath("room.db") + val builder = Room.databaseBuilder( + context = ctx, + name = dbFile.absolutePath, + ) + + Database.build(builder) + } +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt new file mode 100644 index 0000000..006b749 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt @@ -0,0 +1,83 @@ +package moe.lava.banksia.core.room + +import androidx.room.AutoMigration +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.room.util.foreignKeyCheck +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import androidx.sqlite.execSQL +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import moe.lava.banksia.core.room.converter.RouteTypeConverter +import moe.lava.banksia.core.room.dao.RouteDao +import moe.lava.banksia.core.room.dao.ServiceDao +import moe.lava.banksia.core.room.dao.ServiceExceptionDao +import moe.lava.banksia.core.room.dao.ShapeDao +import moe.lava.banksia.core.room.dao.StopDao +import moe.lava.banksia.core.room.dao.StopTimeDao +import moe.lava.banksia.core.room.dao.TripDao +import moe.lava.banksia.core.room.dao.VersionMetadataDao +import moe.lava.banksia.core.room.entity.RouteEntity +import moe.lava.banksia.core.room.entity.ServiceEntity +import moe.lava.banksia.core.room.entity.ServiceExceptionEntity +import moe.lava.banksia.core.room.entity.ShapeEntity +import moe.lava.banksia.core.room.entity.StopEntity +import moe.lava.banksia.core.room.entity.StopTimeEntity +import moe.lava.banksia.core.room.entity.TripEntity +import moe.lava.banksia.core.room.entity.VersionMetadataEntity +import androidx.room.Database as DatabaseAnnotation + +@DatabaseAnnotation( + version = 11, + entities = [ + RouteEntity::class, + ServiceEntity::class, + ServiceExceptionEntity::class, + ShapeEntity::class, + StopEntity::class, + StopTimeEntity::class, + TripEntity::class, + VersionMetadataEntity::class, + ], + autoMigrations = [ + AutoMigration(from = 1, to = 2), + AutoMigration(from = 2, to = 3), + AutoMigration(from = 9, to = 10), + ] +) +@TypeConverters(RouteTypeConverter::class) +abstract class Database : RoomDatabase() { + abstract val versionMetadataDao: VersionMetadataDao + abstract val routeDao: RouteDao + abstract val serviceDao: ServiceDao + abstract val serviceExceptionDao: ServiceExceptionDao + abstract val shapeDao: ShapeDao + abstract val stopDao: StopDao + abstract val stopTimeDao: StopTimeDao + abstract val tripDao: TripDao + + companion object { + fun build(base: Builder) = + base.fallbackToDestructiveMigration(true) + .setDriver(BundledSQLiteDriver()) + .setQueryCoroutineContext(Dispatchers.IO) + .addMigrations(MIGRATION_10_11) +// .fallbackToDestructiveMigration(true) + .build() + } +} + +val MIGRATION_10_11 = object : Migration(10, 11) { + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_Stop` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)") + connection.execSQL("INSERT INTO `_new_Stop` (`id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode`) SELECT `id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode` FROM `Stop`") + connection.execSQL("UPDATE `_new_Stop` SET `parent` = NULL WHERE `parent` == \"\"") + connection.execSQL("DROP TABLE `Stop`") + connection.execSQL("ALTER TABLE `_new_Stop` RENAME TO `Stop`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `Stop` (`parent`)") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `Trip` (`serviceId`)") + foreignKeyCheck(connection, "Stop") + } +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt new file mode 100644 index 0000000..bfe32a9 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt @@ -0,0 +1,7 @@ +package moe.lava.banksia.core.room + +import org.koin.core.component.KoinComponent + +expect class DatabaseManager() : KoinComponent { + val database: Database +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt new file mode 100644 index 0000000..85c56fc --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt @@ -0,0 +1,18 @@ +package moe.lava.banksia.core.room + +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val roomDiModule = module { + singleOf(::DatabaseManager) + factory { get().database } + + factory { get().versionMetadataDao } + factory { get().routeDao } + factory { get().serviceDao } + factory { get().serviceExceptionDao } + factory { get().shapeDao } + factory { get().stopDao } + factory { get().stopTimeDao } + factory { get().tripDao } +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt new file mode 100644 index 0000000..f588a66 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt @@ -0,0 +1,12 @@ +package moe.lava.banksia.core.room.converter + +import androidx.room.TypeConverter +import moe.lava.banksia.core.model.RouteType + +object RouteTypeConverter { + @TypeConverter + fun from(value: Int) = RouteType.from(value) + + @TypeConverter + fun to(routeType: RouteType) = routeType.value +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt new file mode 100644 index 0000000..b914cff --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt @@ -0,0 +1,43 @@ +package moe.lava.banksia.core.room.converter + +import androidx.room.TypeConverter +import moe.lava.banksia.core.model.ShapePath +import moe.lava.banksia.core.util.Point + +object ShapePathConverter { + @TypeConverter + fun from(value: ByteArray): ShapePath { + return value + .asIterable() + .chunked(8) { + (it[0].toLong() and 0xFF) or + (it[1].toLong() and 0xFF shl 8) or + (it[2].toLong() and 0xFF shl 16) or + (it[3].toLong() and 0xFF shl 24) or + (it[4].toLong() and 0xFF shl 32) or + (it[5].toLong() and 0xFF shl 40) or + (it[6].toLong() and 0xFF shl 48) or + (it[7].toLong() and 0xFF shl 56) + } + .map { Double.fromBits(it) } + .chunked(2) + .map { (lat, lng) -> Point(lat, lng) } + } + + @TypeConverter + fun to(path: ShapePath): ByteArray { + return path + .flatMap { (lat, lng) -> listOf(lat.toBits(), lng.toBits()) } + .flatMap { i -> listOf( + i.toByte(), + (i shr 8).toByte(), + (i shr 16).toByte(), + (i shr 24).toByte(), + (i shr 32).toByte(), + (i shr 40).toByte(), + (i shr 48).toByte(), + (i shr 56).toByte(), + ) } + .toByteArray() + } +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt new file mode 100644 index 0000000..1c9d4df --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt @@ -0,0 +1,61 @@ +package moe.lava.banksia.core.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.core.room.entity.RouteEntity +import moe.lava.banksia.core.room.entity.StopEntity + +@Dao +interface RouteDao { + @Query("SELECT * FROM Route") + suspend fun getAll(): List + + @Query("SELECT * FROM Route WHERE id == :id") + suspend fun get(id: String): RouteEntity? + + @Insert + suspend fun insertAll(vararg routes: RouteEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg routes: RouteEntity) + + @Delete + suspend fun delete(route: RouteEntity) + + @Query("DELETE FROM Route") + suspend fun deleteAll() + + @Query("SELECT * FROM Route INNER JOIN Trip on Route.id == Trip.routeId WHERE Trip.id == :tripId") + suspend fun getByTrip(tripId: String): RouteEntity? + + @Query(""" + SELECT Stop.* FROM Stop + INNER JOIN StopTime ON StopTime.stopId == Stop.id + INNER JOIN Trip ON Trip.id == StopTime.tripId + WHERE Trip.routeId == :id + GROUP BY Stop.id + """) + suspend fun stops(id: String): List + + // I vibecoded this, sorry + @Query(""" + WITH Tree AS ( + SELECT Stop.* FROM Stop + INNER JOIN StopTime ON StopTime.stopId == Stop.id + INNER JOIN Trip ON Trip.id == StopTime.tripId + WHERE Trip.routeId == :id + GROUP BY Stop.id + + UNION ALL + + SELECT s.* + FROM Stop s + INNER JOIN Tree t ON s.id = t.parent + ) + SELECT DISTINCT * FROM Tree WHERE parent IS NULL; + """) + suspend fun stopsParent(id: String): List +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt new file mode 100644 index 0000000..e459cdf --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt @@ -0,0 +1,29 @@ +package moe.lava.banksia.core.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.core.room.entity.ServiceEntity + +@Dao +interface ServiceDao { + @Query("SELECT * FROM Service") + suspend fun getAll(): List + + @Query("SELECT * FROM Service WHERE id == :id") + suspend fun get(id: String): ServiceEntity? + + @Insert + suspend fun insertAll(vararg services: ServiceEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg services: ServiceEntity) + + @Delete + suspend fun delete(service: ServiceEntity) + + @Query("DELETE FROM Service") + suspend fun deleteAll() +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt new file mode 100644 index 0000000..86feb75 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt @@ -0,0 +1,29 @@ +package moe.lava.banksia.core.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.core.room.entity.ServiceExceptionEntity + +@Dao +interface ServiceExceptionDao { + @Query("SELECT * FROM ServiceException") + suspend fun getAll(): List + + @Query("SELECT * FROM ServiceException WHERE serviceId == :id") + suspend fun get(id: String): List + + @Insert + suspend fun insertAll(vararg exceptions: ServiceExceptionEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg exceptions: ServiceExceptionEntity) + + @Delete + suspend fun delete(service: ServiceExceptionEntity) + + @Query("DELETE FROM ServiceException") + suspend fun deleteAll() +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt new file mode 100644 index 0000000..446a923 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt @@ -0,0 +1,26 @@ +package moe.lava.banksia.core.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.core.room.entity.ShapeEntity + +@Dao +interface ShapeDao { + @Query("SELECT * FROM Shape WHERE id == :id") + suspend fun get(id: String): ShapeEntity? + + @Insert + suspend fun insertAll(vararg shapes: ShapeEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg shapes: ShapeEntity) + + @Delete + suspend fun delete(shape: ShapeEntity) + + @Query("DELETE FROM Shape") + suspend fun deleteAll() +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt new file mode 100644 index 0000000..7edb560 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt @@ -0,0 +1,42 @@ +package moe.lava.banksia.core.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.core.room.entity.StopEntity + +@Dao +interface StopDao { + @Query("SELECT * FROM Stop") + suspend fun getAll(): List + + @Query(""" + SELECT * FROM Stop + WHERE platformCode <> "" + AND parent IS NULL + """) + suspend fun getAllParentless(): List + + @Query("SELECT * FROM Stop WHERE id == :id") + suspend fun get(id: String): StopEntity? + + @Query("SELECT * FROM Stop WHERE id IN (:ids)") + suspend fun get(ids: List): List + + @Insert + suspend fun insertAll(vararg stops: StopEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg stops: StopEntity) + + @Delete + suspend fun delete(stop: StopEntity) + + @Query("DELETE FROM Stop") + suspend fun deleteAll() + + @Query("UPDATE Stop SET parent = :parent WHERE id IN (:ids)") + suspend fun updateParents(ids: List, parent: String) +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt new file mode 100644 index 0000000..4670b6e --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt @@ -0,0 +1,46 @@ +package moe.lava.banksia.core.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.core.room.entity.StopTimeEntity + +@Dao +interface StopTimeDao { + @Query("SELECT * FROM StopTime") + suspend fun getAll(): List + + @Query("SELECT * FROM StopTime WHERE tripId == :tripId") + suspend fun getForTrip(tripId: String): StopTimeEntity? + + @Query("SELECT * FROM StopTime WHERE tripId IN (:tripIds)") + suspend fun getForTrips(tripIds: List): List + + @Query("SELECT * FROM StopTime WHERE stopId == :stopId") + suspend fun getForStop(stopId: String): List + + @Query(""" + SELECT DISTINCT StopTime.* FROM StopTime + INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` + INNER JOIN Trip ON Trip.serviceId == Service.id + LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date + WHERE StopTime.tripId == Trip.id + AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) + AND ServiceException.type IS NULL + """) + suspend fun getForStopDated(stopId: String, days: Int, date: Int): List + + @Insert + suspend fun insertAll(vararg stopTimes: StopTimeEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg stopTimes: StopTimeEntity) + + @Delete + suspend fun delete(stopTime: StopTimeEntity) + + @Query("DELETE FROM StopTime") + suspend fun deleteAll() +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt new file mode 100644 index 0000000..1798f61 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt @@ -0,0 +1,32 @@ +package moe.lava.banksia.core.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.core.room.entity.TripEntity + +@Dao +interface TripDao { + @Query("SELECT * FROM Trip") + suspend fun getAll(): List + + @Query("SELECT * FROM Trip WHERE id == :id") + suspend fun get(id: String): TripEntity? + + @Query("SELECT * FROM Trip WHERE routeId == :id") + suspend fun getByRoute(id: String): List + + @Insert + suspend fun insertAll(vararg trips: TripEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg trips: TripEntity) + + @Delete + suspend fun delete(trip: TripEntity) + + @Query("DELETE FROM Trip") + suspend fun deleteAll() +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt new file mode 100644 index 0000000..357770d --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt @@ -0,0 +1,27 @@ +package moe.lava.banksia.core.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.core.room.entity.VersionMetadataEntity + +@Dao +interface VersionMetadataDao { + @Query("SELECT * FROM VersionMetadata WHERE type == :type") + suspend fun get(type: String): VersionMetadataEntity? + + @Query("SELECT * FROM VersionMetadata") + suspend fun getAll(): List + + @Insert(onConflict = REPLACE) + suspend fun update(vararg data: VersionMetadataEntity) + + suspend fun update(vararg data: Pair) { + update(*data.map { (type, lastUpdated) -> VersionMetadataEntity(type, lastUpdated) }.toTypedArray()) + } + + suspend fun update(lastUpdated: Long, types: Collection) { + update(*types.map { VersionMetadataEntity(it, lastUpdated) }.toTypedArray()) + } +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt new file mode 100644 index 0000000..8feda0b --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt @@ -0,0 +1,18 @@ +package moe.lava.banksia.core.room.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import moe.lava.banksia.core.model.Route +import moe.lava.banksia.core.model.RouteType + +@Entity("Route") +data class RouteEntity( + @PrimaryKey val id: String, + val type: RouteType, + val number: String?, + val name: String, +) { + fun asModel() = Route(id, type, number, name) +} + +fun Route.asEntity() = RouteEntity(id, type, number, name) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt new file mode 100644 index 0000000..4a1c8b7 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt @@ -0,0 +1,31 @@ +package moe.lava.banksia.core.room.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.datetime.LocalDate +import moe.lava.banksia.core.model.Service +import moe.lava.banksia.core.util.deserialiseDaysBitflag +import moe.lava.banksia.core.util.serialise + +@Entity("Service") +data class ServiceEntity( + @PrimaryKey val id: String, + @ColumnInfo(index = true) val days: Int, + val start: Int, + val end: Int, +) { + fun asModel() = Service( + id, + days.deserialiseDaysBitflag(), + LocalDate.fromEpochDays(start), + LocalDate.fromEpochDays(end), + ) +} + +fun Service.asEntity() = ServiceEntity( + id, + days.serialise(), + start.toEpochDays().toInt(), + end.toEpochDays().toInt(), +) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt new file mode 100644 index 0000000..1ac45d3 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt @@ -0,0 +1,28 @@ +package moe.lava.banksia.core.room.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import kotlinx.datetime.LocalDate +import moe.lava.banksia.core.model.ServiceException + +@Entity( + "ServiceException", + primaryKeys = ["serviceId", "date"] +) +data class ServiceExceptionEntity( + @ColumnInfo(index = true) val serviceId: String, + val date: Int, + @ColumnInfo(index = true) val type: Int, +) { + fun asModel() = ServiceException( + serviceId, + LocalDate.fromEpochDays(date), + type, + ) +} + +fun ServiceException.asEntity() = ServiceExceptionEntity( + serviceId, + date.toEpochDays().toInt(), + type, +) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt new file mode 100644 index 0000000..a19147d --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt @@ -0,0 +1,19 @@ +package moe.lava.banksia.core.room.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import moe.lava.banksia.core.model.Shape +import moe.lava.banksia.core.model.ShapePath +import moe.lava.banksia.core.room.converter.ShapePathConverter + +@Entity("Shape") +@TypeConverters(ShapePathConverter::class) +data class ShapeEntity( + @PrimaryKey val id: String, + val path: ShapePath, +) { + fun asModel() = Shape(id, path) +} + +fun Shape.asEntity() = ShapeEntity(id, path) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt new file mode 100644 index 0000000..f59c5da --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt @@ -0,0 +1,36 @@ +package moe.lava.banksia.core.room.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.Companion.SET_NULL +import androidx.room.PrimaryKey +import moe.lava.banksia.core.model.Stop +import moe.lava.banksia.core.util.Point + +@Entity( + "Stop", + foreignKeys = [ + ForeignKey( + StopEntity::class, + parentColumns = ["id"], + childColumns = ["parent"], + onDelete = SET_NULL, + deferred = true, + ), + ] +) +data class StopEntity( + @PrimaryKey val id: String, + val name: String, + val lat: Double, + val lng: Double, + @ColumnInfo(index = true) val parent: String?, + val hasWheelChairBoarding: Boolean, + val level: String, + val platformCode: String, +) { + fun asModel() = Stop(id, name, Point(lat, lng), parent, hasWheelChairBoarding, level, platformCode) +} + +fun Stop.asEntity() = StopEntity(id, name, pos.lat, pos.lng, parent, hasWheelChairBoarding, level, platformCode) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt new file mode 100644 index 0000000..d96036d --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt @@ -0,0 +1,53 @@ +package moe.lava.banksia.core.room.entity + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.Companion.CASCADE +import androidx.room.Index +import kotlinx.serialization.ExperimentalSerializationApi +import moe.lava.banksia.core.model.FutureTime +import moe.lava.banksia.core.model.FutureTime.Companion.asInt +import moe.lava.banksia.core.model.StopTime + +@Entity( + "StopTime", + primaryKeys = ["tripId", "stopId"], + indices = [ + Index("tripId", unique = false), + Index("stopId", unique = false), + ], + foreignKeys = [ + ForeignKey(TripEntity::class, parentColumns = ["id"], childColumns = ["tripId"], onDelete = CASCADE), + ForeignKey(StopEntity::class, parentColumns = ["id"], childColumns = ["stopId"], onDelete = CASCADE), + ] +) +data class StopTimeEntity( + val tripId: String, + val stopId: String, + val arrivalTime: Int, + val departureTime: Int, + val headsign: String?, + val pickupType: Int, + val dropOffType: Int, +) { + fun asModel() = StopTime( + tripId, + stopId, + FutureTime.fromInt(arrivalTime), + FutureTime.fromInt(departureTime), + headsign, + pickupType, + dropOffType, + ) +} + +@OptIn(ExperimentalSerializationApi::class) +fun StopTime.asEntity() = StopTimeEntity( + tripId, + stopId, + arrivalTime.asInt(), + departureTime.asInt(), + headsign, + pickupType, + dropOffType, +) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt new file mode 100644 index 0000000..7928c60 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt @@ -0,0 +1,49 @@ +package moe.lava.banksia.core.room.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.Companion.CASCADE +import androidx.room.Index +import androidx.room.PrimaryKey +import moe.lava.banksia.core.model.Trip + +@Entity( + "Trip", + foreignKeys = [ + ForeignKey(RouteEntity::class, parentColumns = ["id"], childColumns = ["routeId"], onDelete = CASCADE), + ForeignKey(ServiceEntity::class, parentColumns = ["id"], childColumns = ["serviceId"], onDelete = CASCADE), + ForeignKey(ShapeEntity::class, parentColumns = ["id"], childColumns = ["shapeId"], onDelete = CASCADE), + ], + indices = [Index("shapeId"), Index("serviceId")], +) +data class TripEntity( + @PrimaryKey val id: String, + @ColumnInfo(index = true) val routeId: String, + val serviceId: String, + val shapeId: String?, + val tripHeadsign: String, + val directionId: String, + val blockId: String, + val wheelchairAccessible: String, +) + +fun Trip.Companion.from(tripEntity: TripEntity, serviceEntity: ServiceEntity): Trip { + if (tripEntity.serviceId != serviceEntity.id) { + throw IllegalArgumentException("trip and service id mismatch (${tripEntity.serviceId} != ${serviceEntity.id})") + } + return with(tripEntity) { + Trip( + id = id, + routeId = routeId, + service = serviceEntity.asModel(), + shapeId = shapeId, + tripHeadsign = tripHeadsign, + directionId = directionId, + blockId = blockId, + wheelchairAccessible = wheelchairAccessible + ) + } +} + +fun Trip.asEntity() = TripEntity(id, routeId, service.id, shapeId, tripHeadsign, directionId, blockId, wheelchairAccessible) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt new file mode 100644 index 0000000..1e7cab9 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt @@ -0,0 +1,19 @@ +package moe.lava.banksia.core.room.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import moe.lava.banksia.core.model.VersionMetadata + +@Entity( + "VersionMetadata", +) +data class VersionMetadataEntity( + /** Entity type this metadata applies to */ + @PrimaryKey val type: String, + /** Last updated */ + val lastUpdated: Long, +) { + fun asModel() = VersionMetadata(type, lastUpdated) +} + +fun VersionMetadata.asEntity() = VersionMetadataEntity(type, lastUpdated) diff --git a/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt b/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt new file mode 100644 index 0000000..34e370e --- /dev/null +++ b/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt @@ -0,0 +1,8 @@ +package moe.lava.banksia.core.room + +import org.koin.core.component.KoinComponent + +actual class DatabaseManager actual constructor() : KoinComponent { + actual val database: Database + get() = TODO("Not yet implemented") +} diff --git a/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt b/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt new file mode 100644 index 0000000..360d2dd --- /dev/null +++ b/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt @@ -0,0 +1,69 @@ +package moe.lava.banksia.core.room + +import androidx.room.Room +import androidx.room.RoomDatabase +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import moe.lava.banksia.core.util.error +import org.koin.core.component.KoinComponent +import java.io.File +import kotlin.system.exitProcess + +actual class DatabaseManager : KoinComponent { + private var liveDatabase: Database = Database.build(getBuilder()) + actual val database get() = liveDatabase + + private fun getBuilder(path: String = "./data/room.db"): RoomDatabase.Builder { + val dbFile = File(path) + return Room.databaseBuilder( + name = dbFile.absolutePath, + ).setJournalMode(RoomDatabase.JournalMode.TRUNCATE) + } + + fun makeAlt() = Database.build(getBuilder("./data/room_alt.db")) + + private fun deleteAll(file: File): Boolean { + val r1 = file.takeIf { it.exists() }?.delete() + val r2 = File(file.parentFile, file.name + ".lck").takeIf { it.exists() }?.delete() + val r3 = File(file.parentFile, file.name + "-journal").takeIf { it.exists() }?.delete() + return r1 != false && r2 != false && r3 != false + } + + private fun renameAll(from: File, to: File): Boolean { + val r1 = from.takeIf { it.exists() }?.renameTo(to) + val r2 = File(from.parentFile, from.name + ".lck").takeIf { it.exists() }?.renameTo(File(to.parentFile, to.name + ".lck")) + val r3 = File(from.parentFile, from.name + "-journal").takeIf { it.exists() }?.renameTo(File(to.parentFile, to.name + "-journal")) + return r1 != false && r2 != false && r3 != false + } + + fun swap(scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) { + val live = File("./data/room.db") + val alt = File("./data/room_alt.db") + val old = File("./data/room_old.db") + + if (!renameAll(live, old)) { + error("DatabaseManager", "Failed to rename database from live to old (${live.absolutePath} -> ${old.absolutePath})") + return + } + if (!renameAll(alt, live)) { + error("DatabaseManager", "Failed to rename database from alt to live, trying to undo.. (${alt.absolutePath} -> ${live.absolutePath})") + if (!live.renameTo(old)) { + error("DatabaseManager", "Failed to undo, critical failure, exiting..") + exitProcess(1) + } + return + } + val oldDatabase = liveDatabase + liveDatabase = Database.build(getBuilder()) + + scope.launch { + delay(5000) + if (!deleteAll(old)) { + error("DatabaseManager", "Failed to unlink old database, stray files! (${old.absolutePath})") + } + oldDatabase.close() + } + } +} diff --git a/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt b/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt deleted file mode 100644 index c47613c..0000000 --- a/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt +++ /dev/null @@ -1,14 +0,0 @@ -package moe.lava.banksia.core.sqld - -import android.content.Context -import app.cash.sqldelight.driver.android.AndroidSqliteDriver -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -actual class DatabaseManager : KoinComponent { - actual val database by lazy { - val ctx = get().applicationContext - val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "${DBNAME}.db") - BanksiaDatabase(driver) - } -} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt deleted file mode 100644 index 983eb58..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt +++ /dev/null @@ -1,7 +0,0 @@ -package moe.lava.banksia.core.sqld - -internal const val DBNAME = "timetable" - -expect class DatabaseManager() { - val database: BanksiaDatabase -} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt deleted file mode 100644 index deee453..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.lava.banksia.core.sqld - -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -val sqldDiModule = module { - singleOf(::DatabaseManager) - factory { get().database } - factory { get().routeQueries } - factory { get().serviceQueries } - factory { get().serviceExceptionQueries } - factory { get().shapeQueries } - factory { get().stopQueries } - factory { get().stoppingPatternQueries } - factory { get().stopTimeQueries } - factory { get().tripQueries } -} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt deleted file mode 100644 index f3a5521..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt +++ /dev/null @@ -1,14 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.model.RouteType -import moe.lava.banksia.core.sqld.Route as DbRoute - -fun DbRoute.asModel() = Route( - id = id, - type = RouteType.from(type.toInt()), - number = number, - name = name, -) - -fun Route.asDb() = DbRoute(id, type.value.toLong(), number, name) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt deleted file mode 100644 index dbda5ea..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt +++ /dev/null @@ -1,21 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.model.Service -import moe.lava.banksia.core.util.deserialiseDaysBitflag -import moe.lava.banksia.core.util.serialise -import moe.lava.banksia.core.sqld.Service as DbService - -fun DbService.asModel() = Service( - id = id, - days = days.toInt().deserialiseDaysBitflag(), - start = LocalDate.fromEpochDays(start), - end = LocalDate.fromEpochDays(end), -) - -fun Service.asDb() = DbService( - id = id, - days = days.serialise().toLong(), - start = start.toEpochDays(), - end = end.toEpochDays(), -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt deleted file mode 100644 index ef0d201..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.model.ServiceException -import moe.lava.banksia.core.sqld.ServiceException as DbServiceException - -fun DbServiceException.asModel() = ServiceException( - serviceId = serviceId, - date = LocalDate.fromEpochDays(date), - type = type.toInt(), -) - -fun ServiceException.asDb() = DbServiceException( - serviceId = serviceId, - type = date.toEpochDays(), - date = type.toLong(), -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt deleted file mode 100644 index 4a8d7db..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt +++ /dev/null @@ -1,52 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.Shape -import moe.lava.banksia.core.model.ShapePath -import moe.lava.banksia.core.util.Point -import moe.lava.banksia.core.sqld.Shape as DbShape - -fun DbShape.asModel() = Shape( - id = id, - path = bytesToPath(path), -) - -fun Shape.asDb() = DbShape( - id = id, - path = bytesFromPath(path), -) - -private fun bytesToPath(value: ByteArray): ShapePath { - return value - .asSequence() - .asIterable() - .chunked(8) { - (it[0].toLong() and 0xFF) or - (it[1].toLong() and 0xFF shl 8) or - (it[2].toLong() and 0xFF shl 16) or - (it[3].toLong() and 0xFF shl 24) or - (it[4].toLong() and 0xFF shl 32) or - (it[5].toLong() and 0xFF shl 40) or - (it[6].toLong() and 0xFF shl 48) or - (it[7].toLong() and 0xFF shl 56) - } - .map { Double.fromBits(it) } - .chunked(2) - .map { (lat, lng) -> Point(lat, lng) } - .toList() -} - -private fun bytesFromPath(path: ShapePath): ByteArray { - return path - .flatMap { (lat, lng) -> listOf(lat.toBits(), lng.toBits()) } - .flatMap { i -> listOf( - i.toByte(), - (i shr 8).toByte(), - (i shr 16).toByte(), - (i shr 24).toByte(), - (i shr 32).toByte(), - (i shr 40).toByte(), - (i shr 48).toByte(), - (i shr 56).toByte(), - ) } - .toByteArray() -} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt deleted file mode 100644 index 3bf6b54..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.util.Point -import moe.lava.banksia.core.sqld.Stop as DbStop - -fun DbStop.asModel() = Stop( - id = id, - name = name, - pos = Point(lat, lng), - parent = parent, - hasWheelChairBoarding = hasWheelChairBoarding == 1L, - level = level, - platformCode = platformCode, -) - -fun Stop.asDb() = DbStop( - id = id, - name = name, - lat = pos.lat, - lng = pos.lng, - parent = parent, - hasWheelChairBoarding = if (hasWheelChairBoarding) 1L else 0L, - level = level, - platformCode = platformCode -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt deleted file mode 100644 index 26d5390..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.FutureTime -import moe.lava.banksia.core.model.FutureTime.Companion.asInt -import moe.lava.banksia.core.model.StopTime -import moe.lava.banksia.core.model.TimeType -import moe.lava.banksia.core.sqld.StopTime as DbStopTime - -fun DbStopTime.asModel() = StopTime( - patternId = patternId, - stopId = stopId, - time = TimeType.Undated( - arrival = FutureTime.fromInt((departureTime + arrivalDelta).toInt()), - departure = FutureTime.fromInt(departureTime.toInt()), - ), - pickupType = pickupType.toInt(), - dropOffType = dropOffType.toInt(), -) - -fun StopTime.Undated.asDb() = DbStopTime( - patternId = patternId, - stopId = stopId, - arrivalDelta = (time.arrival.asInt() - time.departure.asInt()).toLong(), - departureTime = time.departure.asInt().toLong(), - pickupType = pickupType.toLong(), - dropOffType = dropOffType.toLong(), -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt deleted file mode 100644 index d1409a2..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt +++ /dev/null @@ -1,23 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.StopTime -import moe.lava.banksia.core.model.StoppingPattern -import moe.lava.banksia.core.model.TimeType -import moe.lava.banksia.core.sqld.StoppingPattern as DbStoppingPattern - -fun DbStoppingPattern.asModel(stoptimes: List>) = StoppingPattern( - id = id, - routeId = routeId, - shapeId = shapeId, - headsign = headsign, - wheelchairAccessible = wheelchairAccessible == 1L, - stoptimes = stoptimes, -) - -fun StoppingPattern<*>.asDb() = DbStoppingPattern( - id = id, - routeId = routeId, - shapeId = shapeId, - headsign = headsign, - wheelchairAccessible = if (wheelchairAccessible) 1L else 0L, -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt deleted file mode 100644 index b3443fb..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.Service -import moe.lava.banksia.core.model.StoppingPattern -import moe.lava.banksia.core.model.Trip -import moe.lava.banksia.core.sqld.Trip as DbTrip - -fun DbTrip.asModel(pattern: StoppingPattern.Undated, service: Service): Trip.Undated { - if (serviceId != service.id) { - throw IllegalArgumentException("trip and service id mismatch (${serviceId} != ${service.id})") - } - return Trip( - id = gtfsId, - pattern = pattern, - service = service, - directionId = directionId.toInt(), - blockId = blockId.toString(), - ) -} - -fun Trip.Undated.asDb() = DbTrip( - gtfsId = id, - patternId = pattern.id, - serviceId = service.id, - directionId = directionId.toLong(), - blockId = blockId?.toLong(), -) diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq deleted file mode 100644 index e607975..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq +++ /dev/null @@ -1,20 +0,0 @@ -CREATE TABLE Route ( - id TEXT PRIMARY KEY NOT NULL, - type INTEGER NOT NULL, - number TEXT, - name TEXT NOT NULL -); - -getAll: -SELECT * FROM Route; - -get: -SELECT * FROM Route WHERE id == ?; - -getByPattern: -SELECT Route.* FROM Route -INNER JOIN StoppingPattern ON Route.id == StoppingPattern.routeId -WHERE StoppingPattern.id == :patternId; - -insert: -INSERT OR REPLACE INTO Route VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq deleted file mode 100644 index a1c5fad..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE Service ( - id TEXT PRIMARY KEY NOT NULL, - days INTEGER NOT NULL, - start INTEGER NOT NULL, - end INTEGER NOT NULL -); - -CREATE INDEX idx_Service_days ON Service (days); - -insert: -INSERT INTO Service VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq deleted file mode 100644 index 332f198..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE ServiceException ( - serviceId TEXT NOT NULL, - type INTEGER NOT NULL, - date INTEGER NOT NULL, - PRIMARY KEY (serviceId, type) -); - -insert: -INSERT INTO ServiceException VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq deleted file mode 100644 index 8734200..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE Shape ( - id TEXT PRIMARY KEY NOT NULL, - path BLOB NOT NULL -); - -insert: -INSERT INTO Shape VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq deleted file mode 100644 index 4af5c50..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq +++ /dev/null @@ -1,54 +0,0 @@ -CREATE TABLE Stop ( - id TEXT PRIMARY KEY NOT NULL, - name TEXT NOT NULL, - lat REAL NOT NULL, - lng REAL NOT NULL, - parent TEXT REFERENCES Stop(id), - hasWheelChairBoarding INTEGER NOT NULL, - level TEXT, - platformCode TEXT -); - -CREATE INDEX idx_Stop_parent ON Stop (parent); - -getAll: -SELECT * FROM Stop; - -getAllParentless: -SELECT * FROM Stop WHERE platformCode IS NOT NULL AND parent IS NULL; - -get: -SELECT * FROM Stop WHERE id == ?; - -getMany: -SELECT * FROM Stop WHERE id IN ?; - -insert: -INSERT INTO Stop VALUES ?; - -updateParents: -UPDATE Stop SET parent = ? WHERE id IN ?; - -getByRoute: -SELECT Stop.* FROM Stop -INNER JOIN StopTime ON StopTime.stopId == Stop.id -INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId -WHERE StoppingPattern.routeId == :id -GROUP BY Stop.id; - --- I vibecoded this, sorry -getParentsByRoute: -WITH RECURSIVE Tree AS ( - SELECT Stop.* FROM Stop - INNER JOIN StopTime ON StopTime.stopId == Stop.id - INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId - WHERE StoppingPattern.routeId == :id - GROUP BY Stop.id - - UNION ALL - - SELECT s.* - FROM Stop s - INNER JOIN Tree t ON s.id = t.parent -) -SELECT DISTINCT * FROM Tree WHERE parent IS NULL; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq deleted file mode 100644 index 06bd76b..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq +++ /dev/null @@ -1,45 +0,0 @@ -CREATE TABLE StopTime ( - patternId INTEGER NOT NULL REFERENCES StoppingPattern (id), - stopId TEXT NOT NULL REFERENCES Stop (id), - arrivalDelta INTEGER NOT NULL, - departureTime INTEGER NOT NULL, - pickupType INTEGER NOT NULL, - dropOffType INTEGER NOT NULL, - PRIMARY KEY (patternId, stopId) -) WITHOUT ROWID; - -CREATE INDEX idx_StopTime_stopId ON StopTime (stopId); - -insert: -INSERT OR REPLACE INTO StopTime VALUES ?; - -getForStopDated: -SELECT DISTINCT StopTime.* FROM StopTime -INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` -LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date -INNER JOIN Trip ON Trip.serviceId == Service.id -INNER JOIN StoppingPattern ON StoppingPattern.id == Trip.patternId -WHERE StopTime.patternId == StoppingPattern.id - AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) - AND ServiceException.type IS NULL; - -getExtendedForStop: -SELECT DISTINCT - StopTime.patternId, - StopTime.arrivalDelta, - StopTime.departureTime, - StoppingPattern.headsign, - Route.type AS routeType, - Route.number AS routeNumber, - Route.name AS routeName, - Stop.platformCode AS stopPlatformCode -FROM StopTime -INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` -LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date -INNER JOIN Trip ON Trip.serviceId == Service.id -INNER JOIN StoppingPattern ON StoppingPattern.id == Trip.patternId -INNER JOIN Route ON Route.id == StoppingPattern.routeId -INNER JOIN Stop ON Stop.id == StopTime.stopId -WHERE StopTime.patternId == StoppingPattern.id - AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) - AND ServiceException.type IS NULL; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq deleted file mode 100644 index 9a09e69..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE StoppingPattern ( - id INTEGER PRIMARY KEY NOT NULL, - routeId TEXT NOT NULL REFERENCES Route (id), - shapeId TEXT NOT NULL REFERENCES Shape (id), - headsign TEXT NOT NULL, - wheelchairAccessible INTEGER NOT NULL -); - -insert: -INSERT OR REPLACE INTO StoppingPattern VALUES ?; - -get: -SELECT * FROM StoppingPattern WHERE id == :id; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq deleted file mode 100644 index c53b62a..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE Trip ( - gtfsId TEXT PRIMARY KEY NOT NULL, - patternId INTEGER NOT NULL REFERENCES StoppingPattern (id), - serviceId TEXT NOT NULL REFERENCES Service (id), - blockId INTEGER, - directionId INTEGER NOT NULL -); - -CREATE INDEX idx_Trip_patternId ON Trip (patternId); -CREATE INDEX idx_Trip_serviceId ON Trip (serviceId); - -insert: -INSERT OR REPLACE INTO Trip VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/schema/1.db b/core/sqld/src/commonMain/sqldelight/schema/1.db deleted file mode 100644 index feaacb3..0000000 Binary files a/core/sqld/src/commonMain/sqldelight/schema/1.db and /dev/null differ diff --git a/core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt b/core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt deleted file mode 100644 index 9ce0627..0000000 --- a/core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.lava.banksia.core.sqld - -import app.cash.sqldelight.driver.native.NativeSqliteDriver -import org.koin.core.component.KoinComponent - -actual class DatabaseManager : KoinComponent { - actual val database by lazy { - val driver = NativeSqliteDriver(BanksiaDatabase.Schema, "${DBNAME}.db") - BanksiaDatabase(driver) - } -} diff --git a/core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt b/core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt deleted file mode 100644 index 61d9e95..0000000 --- a/core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt +++ /dev/null @@ -1,56 +0,0 @@ -package moe.lava.banksia.core.sqld - -import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import moe.lava.banksia.core.util.error -import org.koin.core.component.KoinComponent -import java.io.File -import java.util.Properties -import kotlin.system.exitProcess - -actual class DatabaseManager : KoinComponent { - private var driver = connect() - actual val database get() = BanksiaDatabase(driver) - - private fun connect(path: String = "./data/${DBNAME}.db") = - JdbcSqliteDriver("jdbc:sqlite:${path}", Properties(), BanksiaDatabase.Schema) - .apply { execute(null, "PRAGMA journal_mode = OFF;", 0) } - - fun makeAlt() = run { - File("./data/${DBNAME}_alt.db").takeIf { it.exists() }?.delete() - val driver = connect("./data/${DBNAME}_alt.db") - BanksiaDatabase(driver) to { driver.close() } - } - - fun swap(scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) { - val live = File("./data/${DBNAME}.db") - val alt = File("./data/${DBNAME}_alt.db") - val old = File("./data/${DBNAME}_old.db") - - if (live.takeIf { it.exists() }?.renameTo(old) == false) { - error("DatabaseManager", "Failed to rename database from live to old (${live.absolutePath} -> ${old.absolutePath})") - return - } - if (alt.takeIf { it.exists() }?.renameTo(live) == false) { - error("DatabaseManager", "Failed to rename database from alt to live, trying to undo.. (${alt.absolutePath} -> ${live.absolutePath})") - if (!live.renameTo(old)) { - error("DatabaseManager", "Failed to undo, critical failure, exiting..") - exitProcess(1) - } - return - } - val oldDriver = driver - driver = connect() - - scope.launch { - delay(5000) - if (old.takeIf { it.exists() }?.delete() == false) { - error("DatabaseManager", "Failed to unlink old database, stray files! (${old.absolutePath})") - } - oldDriver.close() - } - } -} diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton b/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton index 909f642..15b3c58 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton @@ -6,7 +6,7 @@ object Constants { const val opendataKey: String = "" const val serverUrl: String = "https://banksia.lava.moe/api/" // TODO - var devMode: Boolean = false + const val devMode: Boolean = false const val updateKey: String = "" const val protomapsKey: String = "" } diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt deleted file mode 100644 index 7e23b5d..0000000 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt +++ /dev/null @@ -1,3 +0,0 @@ -package moe.lava.banksia.core.endpoints - -object Endpoint diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt index bbe6fbf..73e6f02 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt @@ -10,6 +10,6 @@ data class Stop( val pos: Point, val parent: String?, val hasWheelChairBoarding: Boolean, - val level: String?, - val platformCode: String?, + val level: String, + val platformCode: String, ) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt index edd7c51..70657dc 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt @@ -1,45 +1,14 @@ package moe.lava.banksia.core.model -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime import kotlinx.serialization.Serializable @Serializable -data class StopTime( - val patternId: Long, +data class StopTime( + val tripId: String, val stopId: String, - val time: T, + val arrivalTime: FutureTime, + val departureTime: FutureTime, + val headsign: String?, val pickupType: Int, val dropOffType: Int, -) { - typealias Dated = StopTime - typealias Undated = StopTime -} - -@Serializable -sealed class TimeType { - @Serializable - data class Undated( - val arrival: FutureTime, - val departure: FutureTime, - ) : TimeType() - - @Serializable - data class Dated( - val arrival: LocalDateTime, - val departure: LocalDateTime, - ) : TimeType() -} - -fun TimeType.Undated.atDate(date: LocalDate) = TimeType.Dated( - arrival = arrival.atDate(date), - departure = departure.atDate(date), -) - -fun StopTime.atDate(date: LocalDate) = StopTime( - patternId = patternId, - stopId = stopId, - time = time.atDate(date), - pickupType = pickupType, - dropOffType = dropOffType, ) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt new file mode 100644 index 0000000..1bd75c6 --- /dev/null +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt @@ -0,0 +1,26 @@ +package moe.lava.banksia.core.model + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.Serializable + +@Serializable +data class StopTimeDated( + val tripId: String, + val stopId: String, + val arrivalTime: LocalDateTime, + val departureTime: LocalDateTime, + val headsign: String?, + val pickupType: Int, + val dropOffType: Int, +) + +fun StopTime.atDate(date: LocalDate) = StopTimeDated( + tripId = tripId, + stopId = stopId, + arrivalTime = arrivalTime.atDate(date), + departureTime = departureTime.atDate(date), + headsign = headsign, + pickupType = pickupType, + dropOffType = dropOffType, +) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt deleted file mode 100644 index 1374cff..0000000 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt +++ /dev/null @@ -1,16 +0,0 @@ -package moe.lava.banksia.core.model - -import kotlinx.serialization.Serializable - -@Serializable -data class StoppingPattern( - val id: Long, - val routeId: String, - val shapeId: String, - val headsign: String, - val wheelchairAccessible: Boolean, - val stoptimes: List>, -) { - typealias Dated = StoppingPattern - typealias Undated = StoppingPattern -} diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt index 752d6d2..753f653 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt @@ -3,13 +3,13 @@ package moe.lava.banksia.core.model import kotlinx.serialization.Serializable @Serializable -data class Trip( +data class Trip( val id: String, - val pattern: StoppingPattern, + val routeId: String, val service: Service, - val directionId: Int, - val blockId: String?, -) { - typealias Dated = Trip - typealias Undated = Trip -} + val shapeId: String?, + val tripHeadsign: String, + val directionId: String, + val blockId: String, + val wheelchairAccessible: String, +) diff --git a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt deleted file mode 100644 index 2f83304..0000000 --- a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.lava.banksia.core.data - -import moe.lava.banksia.core.data.repositories.StopTimeRepository -import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -internal actual val platformModule = module { - singleOf(::StopTimeRepository) - singleOf(::StopTimeRemoteDataSource) -} diff --git a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt deleted file mode 100644 index ecaff8e..0000000 --- a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt +++ /dev/null @@ -1,19 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import kotlinx.coroutines.flow.flow -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource -import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource - -actual class StopTimeRepository internal constructor( - private val local: StopTimeLocalDataSource, - private val remote: StopTimeRemoteDataSource, -) { - actual suspend fun getForStop(id: String, date: LocalDate) = flow { - emit(local.getAtStop(id, date)) - - remote.getAtStop(id, date) - .takeIf { it.isNotEmpty() } - ?.let { emit(it) } - } -} diff --git a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt deleted file mode 100644 index 0c38f64..0000000 --- a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.lava.banksia.core.data.sources.stoptime - -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.request.get -import io.ktor.client.request.parameter -import kotlinx.datetime.LocalDate -import kotlinx.datetime.TimeZone -import kotlinx.datetime.todayIn -import moe.lava.banksia.core.data.dto.ExtendedStopTime -import moe.lava.banksia.core.endpoints.Endpoint -import moe.lava.banksia.core.endpoints.stopTimeByStop -import kotlin.time.Clock - -internal class StopTimeRemoteDataSource( - private val client: HttpClient, -) { - suspend fun getAtStop( - stopId: String, - date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): List { - return client.get(Endpoint.stopTimeByStop(stopId)) { - parameter("date", date) - }.body>() - } -} diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt deleted file mode 100644 index d46affa..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt +++ /dev/null @@ -1,13 +0,0 @@ -package moe.lava.banksia.core.data - -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource -import org.koin.core.module.Module -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -internal expect val platformModule: Module; - -val stopTimeDataDiModule = module { - includes(platformModule) - singleOf(::StopTimeLocalDataSource) -} diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt deleted file mode 100644 index 38de29d..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt +++ /dev/null @@ -1,34 +0,0 @@ -package moe.lava.banksia.core.data.dto - -import kotlinx.datetime.LocalDate -import kotlinx.serialization.Serializable -import moe.lava.banksia.core.model.FutureTime -import moe.lava.banksia.core.model.RouteType -import moe.lava.banksia.core.model.TimeType -import moe.lava.banksia.core.model.atDate -import moe.lava.banksia.core.sqld.GetExtendedForStop - -@Serializable -data class ExtendedStopTime( - val patternId: Long, - val stopPlatformCode: String?, - val time: TimeType.Dated, - val headsign: String?, - val routeType: RouteType, - val routeNumber: String?, - val routeName: String, -) - -// TODO: This probably doesn't belong here -fun GetExtendedForStop.asModel(date: LocalDate) = ExtendedStopTime( - patternId = patternId, - stopPlatformCode = stopPlatformCode, - time = TimeType.Undated( - arrival = FutureTime.fromInt((departureTime + arrivalDelta).toInt()), - departure = FutureTime.fromInt(departureTime.toInt()), - ).atDate(date), - headsign = headsign, - routeType = RouteType.from(routeType.toInt()), - routeNumber = routeNumber, - routeName = routeName, -) diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt deleted file mode 100644 index 6a81c09..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt +++ /dev/null @@ -1,15 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import kotlinx.coroutines.flow.Flow -import kotlinx.datetime.LocalDate -import kotlinx.datetime.TimeZone -import kotlinx.datetime.todayIn -import moe.lava.banksia.core.data.dto.ExtendedStopTime -import kotlin.time.Clock - -expect class StopTimeRepository { - suspend fun getForStop( - id: String, - date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): Flow> -} diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt deleted file mode 100644 index f22dc09..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt +++ /dev/null @@ -1,30 +0,0 @@ -package moe.lava.banksia.core.data.sources.stoptime - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.data.dto.ExtendedStopTime -import moe.lava.banksia.core.data.dto.asModel -import moe.lava.banksia.core.sqld.StopTimeQueries -import moe.lava.banksia.core.util.serialise -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -internal class StopTimeLocalDataSource : KoinComponent { - private val queries get() = get() - - suspend fun getAtStop(stopId: String, date: LocalDate): List { - return withContext(context = Dispatchers.IO) { - queries - .getExtendedForStop( - listOf(date.dayOfWeek).serialise().toLong(), - date.toEpochDays(), - stopId, - ) - .executeAsList() - .map { it.asModel(date) } - .sortedBy { it.time.departure } - } - } -} diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt deleted file mode 100644 index f689b2d..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt +++ /dev/null @@ -1,3 +0,0 @@ -package moe.lava.banksia.core.endpoints - -fun Endpoint.stopTimeByStop(stopId: String) = "stoptimes/by_stop/${stopId}" diff --git a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt deleted file mode 100644 index 70ef406..0000000 --- a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt +++ /dev/null @@ -1,9 +0,0 @@ -package moe.lava.banksia.core.data - -import moe.lava.banksia.core.data.repositories.StopTimeRepository -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -internal actual val platformModule = module { - singleOf(::StopTimeRepository) -} diff --git a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt deleted file mode 100644 index b4c37a6..0000000 --- a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt +++ /dev/null @@ -1,13 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import kotlinx.coroutines.flow.flow -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource - -actual class StopTimeRepository internal constructor( - private val local: StopTimeLocalDataSource, -) { - actual suspend fun getForStop(id: String, date: LocalDate) = flow { - emit(local.getAtStop(id, date)) - } -} diff --git a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt deleted file mode 100644 index 5791855..0000000 --- a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.lava.banksia.server.routes - -import io.ktor.server.response.respond -import io.ktor.server.routing.Route -import io.ktor.server.routing.get -import kotlinx.coroutines.flow.first -import kotlinx.datetime.LocalDate -import kotlinx.datetime.TimeZone -import kotlinx.datetime.todayIn -import moe.lava.banksia.core.data.repositories.StopTimeRepository -import moe.lava.banksia.core.endpoints.Endpoint -import moe.lava.banksia.core.endpoints.stopTimeByStop -import org.koin.ktor.ext.inject -import kotlin.time.Clock - -fun Route.stopTimeRoutes() { - val repo by inject() - - get(Endpoint.stopTimeByStop("{stop_id}")) { - val stopId = call.parameters["stop_id"]!! - val date = call.queryParameters["date"] - ?.let { LocalDate.parse(it, LocalDate.Formats.ISO) } - ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) - val data = repo.getForStop(stopId, date).first() - call.respond(data) - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 483c5d5..e171b55 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] agp = "9.1.0" -android-compileSdk = "37" +android-compileSdk = "36" android-minSdk = "24" -android-targetSdk = "37" +android-targetSdk = "36" androidx-activity= "1.13.0" androidx-lifecycle = "2.10.0" -compose-multiplatform = "1.12.0-alpha02" +compose-multiplatform = "1.11.0-alpha04" composeunstyled = "1.49.6" coroutines = "1.10.2" geo = "0.8.0" @@ -19,11 +19,12 @@ ktor = "3.4.1" logback = "1.5.32" maplibre = "0.12.1" material = "1.7.3" -material3 = "1.11.0-alpha07" +material3 = "1.11.0-alpha04" okio = "3.17.0" playServicesLocation = "21.3.0" +sqlite = "2.6.2" +room = "2.8.4" secretsGradlePlugin = "2.0.1" -sqldelight = "2.3.2" wire = "6.1.0" [libraries] @@ -66,10 +67,10 @@ moko-geo = { module = "dev.icerock.moko:geo", version.ref = "geo" } moko-geo-compose = { module = "dev.icerock.moko:geo-compose", version.ref = "geo" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } +room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } +room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled", version.ref = "sqlite" } secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" } -sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } -sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } -sqldelight-driver-native = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" } ui-backhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.ref = "compose-multiplatform" } [plugins] @@ -82,6 +83,6 @@ kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ktor = { id = "io.ktor.plugin", version.ref = "ktor" } +room = { id = "androidx.room", version.ref = "room" } secretsGradle = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" } -sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } wire = { id = "com.squareup.wire", version.ref = "wire" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 9d2cb78..4150bb2 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -20,9 +20,7 @@ kotlin { dependencies { implementation(projects.core) - implementation(projects.core.data) - implementation(projects.core.sqld) - implementation(projects.core.stoptime) + implementation(projects.core.room) implementation(projects.server.gtfs) implementation(projects.server.gtfsRt) @@ -38,6 +36,8 @@ dependencies { implementation(libs.ktor.server.contentnegotiation) implementation(libs.ktor.server.core) implementation(libs.ktor.server.netty) + implementation(libs.room.runtime) + implementation(libs.sqlite.bundled) testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) } diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index c844499..f8d9832 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -18,7 +18,6 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.serializer import moe.lava.banksia.core.Constants -import moe.lava.banksia.core.model.FutureTime.Companion.asInt import moe.lava.banksia.core.model.Route import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.core.model.Service @@ -26,8 +25,6 @@ import moe.lava.banksia.core.model.ServiceException import moe.lava.banksia.core.model.Shape import moe.lava.banksia.core.model.Stop import moe.lava.banksia.core.model.StopTime -import moe.lava.banksia.core.model.StoppingPattern -import moe.lava.banksia.core.model.TimeType import moe.lava.banksia.core.model.Trip import moe.lava.banksia.core.util.Point import moe.lava.banksia.server.gtfs.structures.GtfsRoute @@ -38,8 +35,6 @@ import moe.lava.banksia.server.gtfs.structures.GtfsStop import moe.lava.banksia.server.gtfs.structures.GtfsStopTime import moe.lava.banksia.server.gtfs.structures.GtfsTrip import java.io.File -import java.nio.ByteBuffer -import java.security.MessageDigest import java.util.zip.ZipFile import kotlin.time.ExperimentalTime @@ -51,7 +46,8 @@ sealed class GtfsData { data class ServiceExceptionChunk(val exceptions: List) : GtfsData() data class ShapeChunk(val shapes: List) : GtfsData() data class StopChunk(val stops: List) : GtfsData() - data class TripChunk(val trips: List) : GtfsData() + data class StopTimeChunk(val stopTimes: List) : GtfsData() + data class TripChunk(val trips: List) : GtfsData() } class GtfsParser( @@ -133,6 +129,7 @@ class GtfsParser( .filter { it.name == "trips.txt" } .flatMap { fd -> parseTrips(fd, services) + .also { emit(GtfsData.TripChunk(it)) } } .associateBy { it.id } @@ -140,53 +137,13 @@ class GtfsParser( .filter { it.name == "stop_times.txt" } .forEach { fd -> log.info("parsing stop times for ${fd.parent}...") - parseStopTimes(fd) { seq -> - val times = ArrayList>(1000100) - seq.forEach { pair -> - val (_, stoptime) = pair - if (times.size > 1000000 && stoptime.patternId == 1L) { - emit(GtfsData.TripChunk(processStoptimes(trips, times))) - times.clear() - } - - times.add(pair) - } - emit(GtfsData.TripChunk(processStoptimes(trips, times))) + parseStopTimes(fd, trips) { seq -> + seq.chunked(1000000) + .forEach { emit(GtfsData.StopTimeChunk(it)) } } } } - private fun hashCalc(headsign: String, stops: List): Long { - val inst = MessageDigest.getInstance("SHA-256") - inst.update(headsign.toByteArray()) - stops.forEach { - inst.update(it.stopId.toByteArray()) - val dint = it.time.departure.asInt() - inst.update((dint).toByte()) - inst.update((dint shr 8).toByte()) - val aint = it.time.arrival.asInt() - inst.update((aint).toByte()) - inst.update((aint shr 8).toByte()) - } - - val buf = inst.digest().slice(0..7).toByteArray() - buf[0] = 0 - buf[1] = 0 - return ByteBuffer.wrap(buf).long - } - - private fun processStoptimes(trips: Map, times: ArrayList>) = - times.groupBy { it.first } - .map { (tripId, pairs) -> - val trip = trips[tripId]!! - val stoptimes = pairs.map { it.second } - val hash = hashCalc(trip.pattern.headsign, stoptimes) - trip.copy(pattern = trip.pattern.copy( - id = hash, - stoptimes = stoptimes.map { it.copy(patternId = hash) } - )) - } - private fun parseRoutes(fd: File) = fd.parseCsv() .map { with(it) { @@ -218,22 +175,21 @@ class GtfsParser( pos = Point(stop_lat, stop_lon), parent = parent_station.ifEmpty { null }, hasWheelChairBoarding = wheelchair_boarding == "1", - level = level_id.ifEmpty { null }, - platformCode = platform_code.ifEmpty { null }, + level = level_id, + platformCode = platform_code, ) } } - private inline fun parseStopTimes(fd: File, block: (Sequence>) -> Unit) = + private inline fun parseStopTimes(fd: File, trips: Map, block: (Sequence) -> Unit) = fd.parseCsvSequence { seq -> seq .map { with(it) { - it.trip_id to StopTime( - patternId = stop_sequence, + StopTime( + tripId = trip_id, stopId = stop_id, - time = TimeType.Undated( - arrival = GtfsStopTime.parseGtfsTime(arrival_time), - departure = GtfsStopTime.parseGtfsTime(departure_time), - ), + arrivalTime = GtfsStopTime.parseGtfsTime(arrival_time), + departureTime = GtfsStopTime.parseGtfsTime(departure_time), + headsign = stop_headsign.ifEmpty { trips[trip_id]!!.tripHeadsign }, pickupType = pickup_type, dropOffType = drop_off_type, ) @@ -254,7 +210,7 @@ class GtfsParser( if (sunday == 1) add(DayOfWeek.SUNDAY) } Service( - id = "${fd.parentFile.name}_${service_id}", + id = service_id, days = days, start = LocalDate.parse(start_date, LocalDate.Formats.ISO_BASIC), end = LocalDate.parse(end_date, LocalDate.Formats.ISO_BASIC), @@ -265,7 +221,7 @@ class GtfsParser( fd.parseCsv() .map { with(it) { ServiceException( - serviceId = "${fd.parentFile.name}_${service_id}", + serviceId = service_id, date = LocalDate.parse(date, LocalDate.Formats.ISO_BASIC), type = exception_type, ) @@ -274,19 +230,15 @@ class GtfsParser( private fun parseTrips(fd: File, services: Map) = fd.parseCsv() .map { with(it) { - Trip.Undated( + Trip( id = trip_id, - pattern = StoppingPattern( - id = 0, - routeId = route_id, - shapeId = shape_id, - headsign = trip_headsign, - wheelchairAccessible = wheelchair_accessible == "1", - stoptimes = listOf() - ), - service = services["${fd.parentFile.name}_${service_id}"]!!, - directionId = direction_id.toInt(), - blockId = block_id.ifEmpty { null }, + routeId = route_id, + service = services[service_id]!!, + shapeId = shape_id.ifEmpty { null }, + tripHeadsign = trip_headsign, + directionId = direction_id, + blockId = block_id, + wheelchairAccessible = wheelchair_accessible, ) } } diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt index c0bbaf2..33da78f 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt @@ -10,7 +10,7 @@ internal data class GtfsStopTime( val arrival_time: String, val departure_time: String, val stop_id: String, - val stop_sequence: Long, + val stop_sequence: Int, val stop_headsign: String, val pickup_type: Int, val drop_off_type: Int, diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt index aaee0a9..7b9229b 100644 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt +++ b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt @@ -41,19 +41,13 @@ internal class GtfsrtArchiver { val time = Instant.fromEpochSeconds(timestamp).toLocalDateTime(TimeZone.currentSystemDefault()) - val (prevWeek, prevDay) = (time.dayOfYear - 1) / 7 to (time.dayOfYear - 1) % 7 - val (nextWeek, nextDay) = time.dayOfYear / 7 to time.dayOfYear % 7 - val base = File(BASE_DIR, type) - val previousParent = File(base, "${time.year}-${prevWeek.toString().padStart(2, '0')}/${prevDay}") - val currentParent = File(base, "${time.year}-${nextWeek.toString().padStart(2, '0')}/${nextDay}") + val previousParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7).toString().padStart(2, '0')}") + val currentParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7 + 1).toString().padStart(2, '0')}") val target = File(currentParent, "${timestamp}.proto") if (previousParent.isDirectory) { enqueueCompression(previousParent) - if (prevWeek != nextWeek) { - enqueueCompression(previousParent.parentFile) - } } if (!target.exists()) { @@ -96,7 +90,6 @@ internal class GtfsrtArchiver { ).start() val exitCode = proc.waitFor() if (exitCode == 0) { - log("CompressJob", "Compressed ${next.absolutePath} to ${next.absolutePath}.tar.zst") if (next.deleteRecursively()) { cmut.withLock { cqueue.remove(next) } } else { diff --git a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt index dedffe5..9e0c957 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -15,26 +15,29 @@ import io.ktor.server.routing.routing import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn import moe.lava.banksia.core.Constants -import moe.lava.banksia.core.sqld.RouteQueries -import moe.lava.banksia.core.sqld.StopQueries -import moe.lava.banksia.core.sqld.mappers.asModel +import moe.lava.banksia.core.model.atDate +import moe.lava.banksia.core.room.dao.RouteDao +import moe.lava.banksia.core.room.dao.StopDao +import moe.lava.banksia.core.room.dao.StopTimeDao +import moe.lava.banksia.core.room.dao.VersionMetadataDao +import moe.lava.banksia.core.util.serialise import moe.lava.banksia.server.di.ServerModules import moe.lava.banksia.server.gtfsrt.GtfsrtService -import moe.lava.banksia.server.routes.stopTimeRoutes import org.koin.dsl.module import org.koin.ktor.ext.get import org.koin.ktor.plugin.Koin +import kotlin.time.Clock fun main() { - if (System.getenv("BANKSIA_PRODUCTION") == "1") Constants.devMode = false - embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module) .start(wait = true) } fun Application.module() { - log.info("devMode: ${Constants.devMode}") install(ContentNegotiation) { json() } @@ -47,8 +50,6 @@ fun Application.module() { launch { get().start(this, !Constants.devMode) } routing { - stopTimeRoutes() - if (Constants.devMode) { get("/fixup") { call.respondText("received") @@ -84,9 +85,25 @@ fun Application.module() { } } + get("/metadata/{type?}") { + val dao = get() + val type = call.parameters["type"] + if (type == null) { + call.respond(dao.getAll().map { it.asModel() }) + return@get + } + + val data = dao.get(type)?.asModel() + if (data == null) { + call.respond(HttpStatusCode.NotFound) + } else { + call.respond(data) + } + } + get("/routes") { val routes = withContext(context = Dispatchers.IO) { - get().getAll().executeAsList() + get().getAll() } val res = routes.map { it.asModel() } call.respond(res) @@ -94,17 +111,26 @@ fun Application.module() { get("/routes/{route_id}") { val routeId = call.parameters["route_id"]!! val route = withContext(context = Dispatchers.IO) { - get().get(routeId).executeAsOneOrNull() + get().get(routeId) } - if (route != null) { + if (route != null) call.respond(route.asModel()) - } else { + else call.respond(HttpStatusCode.NotFound) + } + get("/routes/by_trip/{trip_id}") { + val tripId = call.parameters["trip_id"]!! + val route = withContext(context = Dispatchers.IO) { + get().getByTrip(tripId) } + if (route != null) + call.respond(route.asModel()) + else + call.respond(HttpStatusCode.NotFound) } get("/stops") { val routes = withContext(context = Dispatchers.IO) { - get().getAll().executeAsList() + get().getAll() } val res = routes.map { it.asModel() } call.respond(res) @@ -112,26 +138,41 @@ fun Application.module() { get("/stops/{stop_id}") { val stopId = call.parameters["stop_id"]!! val stop = withContext(context = Dispatchers.IO) { - get().get(stopId).executeAsOneOrNull() + get().get(stopId) } - if (stop != null) { + if (stop != null) call.respond(stop.asModel()) - } else { + else call.respond(HttpStatusCode.NotFound) - } } get("/route_stops/{route_id}") { val routeId = call.parameters["route_id"]!! val useParent = call.queryParameters["parent"] !in listOf("false", "0") val stops = withContext(Dispatchers.IO) { - val queries = get() - if (useParent) { - queries.getParentsByRoute(routeId).executeAsList() - } else { - queries.getByRoute(routeId).executeAsList() - } + val routeDao = get() + if (useParent) + routeDao.stopsParent(routeId) + else + routeDao.stops(routeId) } call.respond(stops.map { it.asModel() }) } + get("/stoptimes/by_stop/{stop_id}") { + val stopId = call.parameters["stop_id"]!! + val date = call.queryParameters["date"] + ?.let { LocalDate.parse(it, LocalDate.Formats.ISO) } + ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) + val times = withContext(context = Dispatchers.IO) { + get() + .getForStopDated( + stopId, + listOf(date.dayOfWeek).serialise(), + date.toEpochDays().toInt(), + ) + .map { it.asModel().atDate(date) } + .sortedBy { it.departureTime } + } + call.respond(times) + } } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt index 97892e0..c74930d 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt @@ -1,16 +1,16 @@ package moe.lava.banksia.server -import moe.lava.banksia.core.sqld.BanksiaDatabase +import moe.lava.banksia.core.room.Database +import moe.lava.banksia.core.room.entity.StopEntity import moe.lava.banksia.core.util.log import java.security.MessageDigest -import moe.lava.banksia.core.sqld.Stop as DbStop class GtfsDataFixer( - private val database: BanksiaDatabase, + private val database: Database, ) { - fun addParentsToStops() { - val queries = database.stopQueries - val stops = queries.getAllParentless().executeAsList() + suspend fun addParentsToStops() { + val dao = database.stopDao + val stops = dao.getAllParentless() stops .groupBy { it.name.split("/")[0] } .filter { (_, stops) -> stops.size > 1 } @@ -19,21 +19,19 @@ class GtfsDataFixer( val avgLng = stops.map { it.lng }.average() val hash = name.sha256().substring(0, 7) val parentId = "bsia:df1:$hash" - val parent = DbStop( + val parent = StopEntity( id = parentId, name = name, lat = avgLat, lng = avgLng, parent = null, - hasWheelChairBoarding = if (stops.all { it.hasWheelChairBoarding == 1L }) 1L else 0L, + hasWheelChairBoarding = stops.all { it.hasWheelChairBoarding }, level = "", platformCode = "", ) log("datafixer", "inserting ${parentId} for ${stops.size} children") - queries.transaction { - queries.insert(parent) - queries.updateParents(parentId, stops.map { it.id }) - } + dao.insertAll(parent) + dao.updateParents(stops.map { it.id }, parentId) } } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index 84fae70..5c8dc37 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -6,13 +6,14 @@ import moe.lava.banksia.core.model.Service import moe.lava.banksia.core.model.ServiceException import moe.lava.banksia.core.model.Shape import moe.lava.banksia.core.model.Stop +import moe.lava.banksia.core.model.StopTime import moe.lava.banksia.core.model.Trip -import moe.lava.banksia.core.sqld.DatabaseManager -import moe.lava.banksia.core.sqld.mappers.asDb +import moe.lava.banksia.core.room.Database +import moe.lava.banksia.core.room.DatabaseManager +import moe.lava.banksia.core.room.entity.asEntity import moe.lava.banksia.server.gtfs.GtfsData import moe.lava.banksia.server.gtfs.GtfsParser import kotlin.time.Clock -import moe.lava.banksia.core.sqld.BanksiaDatabase as Database class GtfsImporter( private val parser: GtfsParser, @@ -20,7 +21,7 @@ class GtfsImporter( private val log: Logger, ) { suspend fun import(url: String, date: Long = Clock.System.now().epochSeconds) { - val (database, close) = dbm.makeAlt() + val database = dbm.makeAlt() parser.update(url).collect { chunk -> when (chunk) { @@ -29,55 +30,53 @@ class GtfsImporter( is GtfsData.ServiceExceptionChunk -> database.addServiceExceptions(chunk.exceptions) is GtfsData.ShapeChunk -> database.addShapes(chunk.shapes) is GtfsData.StopChunk -> database.addStops(chunk.stops) + is GtfsData.StopTimeChunk -> database.addStopTimes(chunk.stopTimes) is GtfsData.TripChunk -> database.addTrips(chunk.trips) } } - close() + database.updateMetadata(date) + database.close() dbm.swap() } - private fun Database.addRoutes(routes: List) { + private suspend fun Database.updateMetadata(date: Long) { + val dao = versionMetadataDao + log.info("updating metadata...") + dao.update(date, listOf("routes", "stops", "shapes", "trips", "stop_times")) + log.info("done") + } + + private suspend fun Database.addRoutes(routes: List) { + val dao = routeDao log.info("inserting routes...") - routeQueries.transaction { - routes.forEach { - routeQueries.insert(it.asDb()) - } - } + dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) log.info("done") } - private fun Database.addServices(services: List) { + private suspend fun Database.addServices(services: List) { + val dao = serviceDao log.info("inserting services...") - serviceQueries.transaction { - services.forEach { - serviceQueries.insert(it.asDb()) - } - } + dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray()) log.info("done") } - private fun Database.addServiceExceptions(exceptions: List) { + private suspend fun Database.addServiceExceptions(exceptions: List) { + val dao = serviceExceptionDao log.info("inserting exceptions...") - serviceExceptionQueries.transaction { - exceptions.forEach { - serviceExceptionQueries.insert(it.asDb()) - } - } + dao.insertOrReplaceAll(*exceptions.map { it.asEntity() }.toTypedArray()) log.info("done") } - private fun Database.addShapes(shapes: List) { + private suspend fun Database.addShapes(shapes: List) { + val dao = shapeDao log.info("inserting shapes...") - shapeQueries.transaction { - shapes.forEach { - shapeQueries.insert(it.asDb()) - } - } + dao.insertOrReplaceAll(*shapes.map { it.asEntity() }.toTypedArray()) log.info("done") } - private fun Database.addStops(stops: List) { + private suspend fun Database.addStops(stops: List) { + val dao = stopDao log.info("inserting stops...") stops .groupBy { it.id } @@ -90,26 +89,21 @@ class GtfsImporter( } } } - - stopQueries.transaction { - stops.forEach { - stopQueries.insert(it.asDb()) - } - } + dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) log.info("done") } - private fun Database.addTrips(trips: List) { + private suspend fun Database.addStopTimes(stopTimes: List) { + val dao = stopTimeDao + log.info("inserting ${stopTimes.size} stoptimes...") + dao.insertOrReplaceAll(*stopTimes.map { it.asEntity() }.toTypedArray()) + log.info("done") + } + + private suspend fun Database.addTrips(trips: List) { + val dao = tripDao log.info("inserting ${trips.size} trips...") - transaction { - trips.forEach { trip -> - stoppingPatternQueries.insert(trip.pattern.asDb()) - trip.pattern.stoptimes.forEach { stoptime -> - stopTimeQueries.insert(stoptime.asDb()) - } - tripQueries.insert(trip.asDb()) - } - } + dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray()) log.info("done") } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index b2593b3..a8a7541 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient -import moe.lava.banksia.core.data.dataDiModule +import moe.lava.banksia.core.room.roomDiModule import moe.lava.banksia.server.GtfsDataFixer import moe.lava.banksia.server.GtfsImporter import moe.lava.banksia.server.gtfs.GtfsParser @@ -11,7 +11,7 @@ import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val ServerModules = module { - includes(dataDiModule) + includes(roomDiModule) single { HttpClient() } singleOf(::GtfsParser) diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml index 6519371..de5d8bf 100644 --- a/server/src/main/resources/logback.xml +++ b/server/src/main/resources/logback.xml @@ -14,7 +14,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + diff --git a/settings.gradle.kts b/settings.gradle.kts index bdb499e..9abcf7f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,8 +37,9 @@ include(":server:gtfs") include(":server:gtfs_rt") include(":core") include(":core:data") -include(":core:stoptime") -include(":core:sqld") +include(":core:data:client") +include(":core:data:server") +include(":core:room") include(":ui") include(":ui:maps") include(":ui:shared") diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index b599bc6..871412d 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -70,8 +70,7 @@ kotlin { implementation(libs.ui.backhandler) implementation(projects.core) - implementation(projects.core.data) - implementation(projects.core.stoptime) + implementation(projects.core.data.client) implementation(projects.ui.maps) implementation(projects.ui.shared) } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt index cff36fb..a2b4d7e 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt @@ -1,12 +1,12 @@ package moe.lava.banksia.ui.di -import moe.lava.banksia.core.data.dataDiModule +import moe.lava.banksia.core.data.clientDataDiModule import moe.lava.banksia.ui.screens.map.MapScreenViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module val AppModule = module { - includes(dataDiModule) + includes(clientDataDiModule) // ViewModel viewModelOf(::MapScreenViewModel) diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt index 9fb37d7..4783998 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeContent import androidx.compose.foundation.layout.size @@ -28,6 +27,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay import kotlin.time.Duration.Companion.milliseconds @@ -69,10 +69,8 @@ fun InfoPanel( modifier = modifier .fillMaxWidth() .padding(horizontal = 24.dp) - .heightIn(min = 350.dp) .onSizeChanged { -// onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) }) - onPeekHeightChange(350.dp) + onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) }) } ) { Box { diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt index 369721c..e7eb04b 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt @@ -1,38 +1,32 @@ package moe.lava.banksia.ui.layout.info -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.togetherWith +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Edit +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SegmentedListItem import androidx.compose.material3.ShapeDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -53,9 +47,7 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes import kotlin.time.Instant -sealed class StopInfoPanelEvent : InfoPanelEvent() { - data object ToggleGrouping : StopInfoPanelEvent() -} +sealed class StopInfoPanelEvent : InfoPanelEvent() data class StopInfoPanelState( val id: String, @@ -64,7 +56,7 @@ data class StopInfoPanelState( val departures: List? = null, ) : InfoPanelState() { override val loading: Boolean - get() = departures.isNullOrEmpty() + get() = departures == null data class DeparturePlatforms( val platform: String, @@ -80,162 +72,94 @@ data class StopInfoPanelState( ) } -@Composable -private fun listColors() = ListItemDefaults.colors( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - selectedContainerColor = MaterialTheme.colorScheme.primary, - selectedContentColor = MaterialTheme.colorScheme.onPrimary, -) - -@Composable @OptIn(ExperimentalMaterial3ExpressiveApi::class) -private fun MonoPlatform( - state: StopInfoPanelState.DeparturePlatforms +@Composable +internal fun StopInfoPanel( + state: StopInfoPanelState, + onEvent: (StopInfoPanelEvent) -> Unit, ) { - val departures = state.departures - val lazyState = LazyListState(firstVisibleItemIndex = - departures.indexOfFirst { - it.time > Clock.System.now() - }.coerceAtLeast(0) + val colors = ListItemDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceContainer, + selectedContainerColor = MaterialTheme.colorScheme.primary, + selectedContentColor = MaterialTheme.colorScheme.onPrimary, ) - LazyColumn( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap), - state = lazyState, - ) { - itemsIndexed(departures) { idx, dep -> - SegmentedListItem( - onClick = {}, - colors = listColors(), - shapes = ListItemDefaults.segmentedShapes( - idx, - departures.size, - ), - supportingContent = { - dep.description?.let { Text(dep.description) } - }, - trailingContent = { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy((-4).dp) - ) { - Text( - text = (dep.time - Clock.System.now()).inWholeMinutes.toString(), - style = MaterialTheme.typography.headlineSmallEmphasized, - ) - Text( - text = "mn", - style = MaterialTheme.typography.labelSmallEmphasized, - ) - } - }, - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(6.dp) - ) { - Box( - Modifier - .clip(ShapeDefaults.ExtraSmall) - .background(dep.routeColour ?: MaterialTheme.colorScheme.surface) - .padding(vertical = 2.dp, horizontal = 4.dp) - ) { - Text( - text = dep.routeName, - style = MaterialTheme.typography.labelSmallEmphasized, - color = MaterialTheme.colorScheme.surface, - ) - } - Text( - text = dep.headsign, - style = MaterialTheme.typography.labelLargeEmphasized, - ) - } - } + + Column(Modifier.fillMaxWidth()) { + Text( + state.name, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Start + ) + state.subname?.let { + Text( + "/ $it", + modifier = Modifier.padding(start = 5.dp), + style = MaterialTheme.typography.titleSmall, + color = Color.Gray, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Start + ) } - } -} + state.departures?.let { departurePlatforms -> + Spacer(Modifier.height(5.dp)) + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap), + ) { + departurePlatforms.forEach { (platform, departures) -> + var expanded by rememberSaveable { mutableStateOf(true) } + val base = ListItemDefaults.segmentedShapes(0, 2) + val large = MaterialTheme.shapes.large -@Composable -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -private fun ManyPlatforms( - state: List, -) { - val expandedList = remember { mutableStateListOf(*Array(state.size) { true }) } - LazyColumn( - modifier = Modifier.fillMaxSize(), - ) { - state.forEachIndexed { idx, depInfo -> - val (platform, departures) = depInfo - val expanded = expandedList[idx] - stickyHeader(key = "header_${depInfo.hashCode()}") { - val base = ListItemDefaults.segmentedShapes(0, 2) - val large = MaterialTheme.shapes.large - - Box( - Modifier - .animateItem() - .background(MaterialTheme.colorScheme.surfaceContainerLow) - .padding(bottom = ListItemDefaults.SegmentedGap) - ) { - SegmentedListItem( - onClick = { expandedList[idx] = !expandedList[idx] }, - colors = listColors(), - shapes = if (expanded) base else base.copy(shape = large), - trailingContent = { - Icon( - painterResource(if (expanded) Res.drawable.arrow_drop_up else Res.drawable.arrow_drop_down), - contentDescription = null, - modifier = Modifier - .background( - if (expanded) MaterialTheme.colorScheme.surface else Color.Transparent, - shape = RoundedCornerShape(100) - ) - .padding(6.dp), - tint = MaterialTheme.colorScheme.onSurface, + if (departurePlatforms.size > 1) { + SegmentedListItem( + onClick = { expanded = !expanded }, + colors = colors, + shapes = if (expanded) base else base.copy(shape = large), + trailingContent = { + Icon( + painterResource(if (expanded) Res.drawable.arrow_drop_up else Res.drawable.arrow_drop_down), + contentDescription = null, + modifier = Modifier + .background( + if (expanded) MaterialTheme.colorScheme.surface else Color.Transparent, + shape = RoundedCornerShape(100) + ) + .padding(6.dp), + tint = MaterialTheme.colorScheme.onSurface, + ) + }, + ) { + Text( + text = platform, + style = MaterialTheme.typography.labelLarge, ) - }, - ) { - Text( - text = platform, - style = MaterialTheme.typography.labelLarge, - ) + } } - } - } - - if (expanded) { - item(key = "items_${depInfo.hashCode()}") { - Column( - modifier = Modifier.animateItem(), - verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap), + AnimatedVisibility( + visible = expanded, + enter = expandVertically(MaterialTheme.motionScheme.fastSpatialSpec()), + exit = shrinkVertically(MaterialTheme.motionScheme.fastSpatialSpec()), ) { - departures.filter { it.time > Clock.System.now() }.take(5) - .forEachIndexed { idx, dep -> + Column(verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap)) { + departures.forEachIndexed { idx, dep -> SegmentedListItem( onClick = {}, - colors = listColors(), +// onClick = { onNavigate(ch) }, + colors = colors, shapes = ListItemDefaults.segmentedShapes( idx + 1, - (departures.size + 1).coerceAtMost(6), + departures.size + 1 ), supportingContent = { dep.description?.let { Text(dep.description) } }, trailingContent = { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy((-4).dp) - ) { - Text( - text = (dep.time - Clock.System.now()).inWholeMinutes.toString(), - style = MaterialTheme.typography.headlineSmallEmphasized, - ) - Text( - text = "mn", - style = MaterialTheme.typography.labelSmallEmphasized, - ) - } + Text( + text = (dep.time - Clock.System.now()).inWholeMinutes.toString(), + style = MaterialTheme.typography.headlineSmallEmphasized, + ) }, ) { Row( @@ -245,10 +169,7 @@ private fun ManyPlatforms( Box( Modifier .clip(ShapeDefaults.ExtraSmall) - .background( - dep.routeColour - ?: MaterialTheme.colorScheme.surface - ) + .background(dep.routeColour ?: MaterialTheme.colorScheme.surface) .padding(vertical = 2.dp, horizontal = 4.dp) ) { Text( @@ -264,66 +185,9 @@ private fun ManyPlatforms( } } } + } } - } - } - item(key = "spacer_${depInfo.hashCode()}") { - Spacer( - modifier = Modifier.animateItem().height(10.dp) - ) - } - } - } -} - -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -internal fun StopInfoPanel( - state: StopInfoPanelState, - onEvent: (StopInfoPanelEvent) -> Unit, -) { - val spec = fadeIn(tween(300, 300)) togetherWith fadeOut(tween(300)) - - AnimatedContent( - targetState = state, - contentKey = { it.id }, - transitionSpec = { spec }, - ) { state -> - Column(Modifier.fillMaxWidth().fillMaxHeight()) { - Row { - Column { - Text( - state.name, - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Start - ) - state.subname?.let { - Text( - "/ $it", - modifier = Modifier.padding(start = 5.dp), - style = MaterialTheme.typography.titleSmall, - color = Color.Gray, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Start - ) - } - } - IconButton( - onClick = { onEvent(StopInfoPanelEvent.ToggleGrouping) }, - ) { Icon(Icons.Default.Edit, null) } - } - Spacer(Modifier.height(10.dp)) - AnimatedContent( - targetState = state.departures, - transitionSpec = { spec }, - ) { departures -> - departures?.let { departurePlatforms -> - if (departurePlatforms.size > 1) { - ManyPlatforms(departurePlatforms) - } else if (departurePlatforms.size == 1) { - MonoPlatform(departurePlatforms[0]) - } + Spacer(Modifier.height(10.dp)) } } } @@ -348,7 +212,7 @@ internal fun StopInfoPanelPreview() { )), StopInfoPanelState.DeparturePlatforms("Platform 2", listOf( StopInfoPanelState.DepartureInfo("237", Color(BUS_ORANGE), "Westall", null, dateIn(7.minutes)), - StopInfoPanelState.DepartureInfo("442", Color(BUS_ORANGE), "Dandenong", null, dateIn(8.minutes)), + StopInfoPanelState.DepartureInfo("442", Color(BUS_ORANGE), "Dandenong", null, dateIn(8.minutes)), )), ), ), diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt index de06381..729630f 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant -import moe.lava.banksia.core.data.dto.ExtendedStopTime import moe.lava.banksia.core.data.repositories.RouteRepository import moe.lava.banksia.core.data.repositories.StopRepository import moe.lava.banksia.core.data.repositories.StopTimeRepository @@ -31,7 +30,6 @@ import moe.lava.banksia.ui.extensions.getUIProperties import moe.lava.banksia.ui.layout.info.InfoPanelEvent import moe.lava.banksia.ui.layout.info.InfoPanelState import moe.lava.banksia.ui.layout.info.RouteInfoPanelState -import moe.lava.banksia.ui.layout.info.StopInfoPanelEvent import moe.lava.banksia.ui.layout.info.StopInfoPanelState import moe.lava.banksia.ui.layout.info.TripInfoPanelState import moe.lava.banksia.ui.map.util.CameraPosition @@ -54,9 +52,6 @@ private data class InternalState( val route: String? = null, val stop: String? = null, val run: String? = null, - - val lastStopDepartures: List? = null, - val stopsGrouped: Boolean = true, ) class MapScreenViewModel( @@ -73,10 +68,6 @@ class MapScreenViewModel( viewModelScope.launch { switchRoute(value.route) } if (value.stop != last.stop) viewModelScope.launch { switchStop(value.stop) } - if (value.lastStopDepartures != last.lastStopDepartures) - viewModelScope.launch { buildDepartures() } - if (value.stopsGrouped != last.stopsGrouped) - viewModelScope.launch { buildDepartures() } if (value.run != last.run) switchRun(value.run) } @@ -113,9 +104,7 @@ class MapScreenViewModel( fun handleEvent(event: InfoPanelEvent) { viewModelScope.launch { - when (event) { - StopInfoPanelEvent.ToggleGrouping -> state = state.copy(stopsGrouped = !state.stopsGrouped) - } +// when (event) { } } } @@ -176,6 +165,7 @@ class MapScreenViewModel( val route = routeRepository.get(routeId) ?: return +// val gtfsRoute = ptvService.route(routeId) iInfoState.update { RouteInfoPanelState( name = route.name, @@ -225,11 +215,11 @@ class MapScreenViewModel( private suspend fun switchStop(id: String?) { if (id == null) { iInfoState.update { InfoPanelState.None } - state = state.copy(lastStopDepartures = null) return } val stop = stopRepository.get(id) +// val stop = ptvService.stop(routeType, stopId) val split = stop.name.split("/") val name = split[0] val subname = split.getOrNull(1) @@ -241,61 +231,57 @@ class MapScreenViewModel( ) } - stopTimeRepository.getForStop(id) - .onEach { departures -> - state = state.copy( - lastStopDepartures = departures - ) - } - .launchIn(viewModelScope) - } - - private fun friendlyPlatform(platform: String) = - platform.takeUnless { it.firstOrNull()?.isDigit() == true } - ?: "Platform $platform" - private fun buildDepartures() { - val rawDepartures = state.lastStopDepartures ?: return - val departures = if (state.stopsGrouped) { - rawDepartures - .groupBy { it.stopPlatformCode } - .mapKeys { (platform) -> platform?.let { friendlyPlatform(it) } } - .entries - .sortedBy { (platform) -> platform } - .map { (platform, deps) -> - StopInfoPanelState.DeparturePlatforms( - platform = platform ?: "", - departures = deps.map { - StopInfoPanelState.DepartureInfo( - routeName = it.routeNumber ?: it.routeName, - routeColour = it.routeType.getUIProperties().colour, - headsign = it.headsign ?: it.routeName, - description = null, - time = it.time.departure.toInstant(TimeZone.currentSystemDefault()), - ) - } - ) + val departures = stopTimeRepository.getForStop(id) + .groupBy { it.stopId } + .mapKeys { (id) -> + val stop = stopRepository.get(id) + if (stop.platformCode.firstOrNull()?.isDigit() == true) { + "Platform " + stop.platformCode + } else { + stop.platformCode } - } else if (rawDepartures.isEmpty()) { - listOf() - } else { - listOf(StopInfoPanelState.DeparturePlatforms(platform = "", departures = rawDepartures.map { dep -> - StopInfoPanelState.DepartureInfo( - routeName = dep.routeNumber ?: dep.routeName, - routeColour = dep.routeType.getUIProperties().colour, - headsign = dep.headsign ?: dep.routeName, - description = dep.stopPlatformCode?.let { friendlyPlatform(it) }, - time = dep.time.departure.toInstant(TimeZone.currentSystemDefault()), - ) - })) - } - - departures.let { departures -> - iInfoState.update { - if (it !is StopInfoPanelState) - it - else - it.copy(departures = departures) } + .entries + .sortedBy { (platform) -> platform } + .map { (platform, deps) -> + StopInfoPanelState.DeparturePlatforms( + platform = platform, + departures = deps.take(5).mapNotNull { + val route = routeRepository.getByTrip(it.tripId) + ?: return@mapNotNull null + StopInfoPanelState.DepartureInfo( + routeName = route.number ?: route.name, + routeColour = route.type.getUIProperties().colour, + headsign = it.headsign ?: route.name, + description = null, + time = it.departureTime.toInstant(TimeZone.currentSystemDefault()), + ) + } + ) + } +// val departures = stopTimeRepository.getForStop(id) +// .filter { !it.headsign.isNullOrBlank() } +// .groupBy { it.headsign!! } +// .map { (headsign, stopTimes) -> +// val now = Clock.System.now() +// val times = stopTimes +// .map { it.arrivalTime.toInstant(TimeZone.currentSystemDefault()) } +// .filter { it >= (now - 1.minutes) } +// .joinToString(" | ") { +// val diff = (it - now).inWholeMinutes.coerceAtLeast(0) +// if (diff >= 65) { +// "${((diff + 30.0) / 60.0).toInt()}hr" +// } else { +// "${diff}mn" +// } +// } +// StopInfoPanelState.DeparturePlatforms(headsign, times) +// } + iInfoState.update { + if (it !is StopInfoPanelState) + it + else + it.copy(departures = departures) } }