Compare commits
No commits in common. "master" and "refactor/huge" have entirely different histories.
master
...
refactor/h
177 changed files with 4896 additions and 2635 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -18,6 +18,6 @@ captures
|
|||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
|
||||
secrets.properties
|
||||
/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt
|
||||
shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt
|
||||
/data/
|
||||
/data
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ plugins {
|
|||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.androidMultiplatformLibrary)
|
||||
alias(libs.plugins.sqldelight)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
android {
|
||||
namespace = "moe.lava.banksia.core.sqld"
|
||||
namespace = "moe.lava.banksia.client"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
compilerOptions {
|
||||
|
|
@ -17,37 +16,28 @@ kotlin {
|
|||
}
|
||||
}
|
||||
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
||||
}
|
||||
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
androidMain.dependencies {
|
||||
implementation(libs.sqldelight.driver.android)
|
||||
implementation(libs.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
implementation(libs.play.services.location)
|
||||
}
|
||||
commonMain.dependencies {
|
||||
implementation(libs.okio)
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
|
||||
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"))
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.contentnegotiation)
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
implementation(projects.shared)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package moe.lava.banksia.client.data.route
|
||||
|
||||
import moe.lava.banksia.model.Route
|
||||
import moe.lava.banksia.room.dao.RouteDao
|
||||
import moe.lava.banksia.room.entity.asEntity
|
||||
|
||||
class RouteLocalDataSource(private val dao: RouteDao) {
|
||||
suspend fun get(id: String) = dao.get(id)
|
||||
suspend fun getAll() = dao.getAll()
|
||||
suspend fun save(vararg routes: Route) = dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray())
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package moe.lava.banksia.client.data.route
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.get
|
||||
import moe.lava.banksia.model.Route
|
||||
|
||||
class RouteRemoteDataSource(val client: HttpClient) {
|
||||
suspend fun get(id: String) = client.get("routes/${id}").body<Route>()
|
||||
suspend fun getAll() = client.get("routes").body<List<Route>>()
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package moe.lava.banksia.client.data.stop
|
||||
|
||||
import moe.lava.banksia.model.Stop
|
||||
import moe.lava.banksia.room.dao.RouteDao
|
||||
import moe.lava.banksia.room.dao.StopDao
|
||||
import moe.lava.banksia.room.entity.asEntity
|
||||
|
||||
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())
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package moe.lava.banksia.core.data.sources.stop
|
||||
package moe.lava.banksia.client.data.stop
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.get
|
||||
import moe.lava.banksia.core.model.Stop
|
||||
import moe.lava.banksia.model.Stop
|
||||
|
||||
internal class StopRemoteDataSource(val client: HttpClient) {
|
||||
class StopRemoteDataSource(val client: HttpClient) {
|
||||
suspend fun get(id: String) = client.get("stops/${id}").body<Stop>()
|
||||
suspend fun getByRoute(id: String) = client.get("route_stops/${id}").body<List<Stop>>()
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package moe.lava.banksia.client.data.stoptime
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.todayIn
|
||||
import moe.lava.banksia.model.StopTimeDated
|
||||
import moe.lava.banksia.model.atDate
|
||||
import moe.lava.banksia.room.dao.StopTimeDao
|
||||
import moe.lava.banksia.util.serialise
|
||||
import kotlin.time.Clock
|
||||
|
||||
class StopTimeLocalDataSource(
|
||||
private val stopTimeDao: StopTimeDao,
|
||||
) {
|
||||
suspend fun getAtStop(
|
||||
stopId: String,
|
||||
date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()),
|
||||
): List<StopTimeDated> {
|
||||
return stopTimeDao
|
||||
.getForStopDated(
|
||||
stopId,
|
||||
listOf(date.dayOfWeek).serialise(),
|
||||
date.toEpochDays().toInt(),
|
||||
)
|
||||
.map { it.asModel().atDate(date) }
|
||||
.sortedBy { it.departureTime }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package moe.lava.banksia.client.data.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.model.StopTimeDated
|
||||
import kotlin.time.Clock
|
||||
|
||||
class StopTimeRemoteDataSource(
|
||||
private val client: HttpClient,
|
||||
) {
|
||||
suspend fun getAtStop(
|
||||
stopId: String,
|
||||
date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()),
|
||||
): List<StopTimeDated> {
|
||||
return client.get("stoptimes/by_stop/${stopId}") {
|
||||
parameter("date", date)
|
||||
}.body<List<StopTimeDated>>()
|
||||
}
|
||||
|
||||
/*suspend fun get(
|
||||
stop: String? = null,
|
||||
trip: String? = null,
|
||||
day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek,
|
||||
): List<StopTime> {
|
||||
return client.get("stoptimes") {
|
||||
stop?.let { parameter("stop", it) }
|
||||
trip?.let { parameter("trip", it) }
|
||||
day?.let { parameter("day", it) }
|
||||
}.body<List<StopTime>>()
|
||||
}*/
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package moe.lava.banksia.client.data.trip
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.datetime.DayOfWeek
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.todayIn
|
||||
import moe.lava.banksia.model.Trip
|
||||
import kotlin.time.Clock
|
||||
|
||||
class TripRemoteDataSource(
|
||||
private val client: HttpClient,
|
||||
) {
|
||||
suspend fun get(
|
||||
day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek,
|
||||
): List<Trip> {
|
||||
return listOf()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.banksia.core.data
|
||||
package moe.lava.banksia.client.di
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.HttpSend
|
||||
|
|
@ -7,22 +7,22 @@ import io.ktor.client.plugins.defaultRequest
|
|||
import io.ktor.client.plugins.plugin
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
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.RouteRepository
|
||||
import moe.lava.banksia.core.data.repositories.StopRepository
|
||||
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.util.log
|
||||
import moe.lava.banksia.Constants
|
||||
import moe.lava.banksia.client.data.route.RouteLocalDataSource
|
||||
import moe.lava.banksia.client.data.route.RouteRemoteDataSource
|
||||
import moe.lava.banksia.client.data.stop.StopLocalDataSource
|
||||
import moe.lava.banksia.client.data.stop.StopRemoteDataSource
|
||||
import moe.lava.banksia.client.data.stoptime.StopTimeLocalDataSource
|
||||
import moe.lava.banksia.client.data.stoptime.StopTimeRemoteDataSource
|
||||
import moe.lava.banksia.client.repository.RouteRepository
|
||||
import moe.lava.banksia.client.repository.StopRepository
|
||||
import moe.lava.banksia.client.repository.StopTimeRepository
|
||||
import moe.lava.banksia.data.ptv.PtvService
|
||||
import moe.lava.banksia.util.log
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val platformModule = module {
|
||||
val ClientModule = module {
|
||||
// HTTP Clients
|
||||
singleOf(::PtvService)
|
||||
single {
|
||||
|
|
@ -49,8 +49,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(::RouteRepository)
|
||||
singleOf(::StopRepository)
|
||||
singleOf(::StopTimeRepository)
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package moe.lava.banksia.client.repository
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.lava.banksia.client.data.route.RouteLocalDataSource
|
||||
import moe.lava.banksia.client.data.route.RouteRemoteDataSource
|
||||
|
||||
class RouteRepository(
|
||||
private val local: RouteLocalDataSource,
|
||||
private val remote: RouteRemoteDataSource,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
suspend fun getAll() = mutex.withLock {
|
||||
local
|
||||
.getAll()
|
||||
.map { it.asModel() }
|
||||
.ifEmpty {
|
||||
remote
|
||||
.getAll()
|
||||
.also { local.save(*it.toTypedArray()) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) }
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package moe.lava.banksia.client.repository
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.lava.banksia.client.data.stop.StopLocalDataSource
|
||||
import moe.lava.banksia.client.data.stop.StopRemoteDataSource
|
||||
|
||||
class StopRepository(
|
||||
private val local: StopLocalDataSource,
|
||||
private val remote: StopRemoteDataSource,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) }
|
||||
suspend fun getByRoute(id: String) = mutex.withLock {
|
||||
local
|
||||
.getByRoute(id)
|
||||
.map { it.asModel() }
|
||||
.ifEmpty { null }
|
||||
?: remote.getByRoute(id)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package moe.lava.banksia.client.repository
|
||||
|
||||
import moe.lava.banksia.client.data.stoptime.StopTimeLocalDataSource
|
||||
import moe.lava.banksia.client.data.stoptime.StopTimeRemoteDataSource
|
||||
import moe.lava.banksia.model.StopTimeDated
|
||||
|
||||
class StopTimeRepository(
|
||||
private val local: StopTimeLocalDataSource,
|
||||
private val remote: StopTimeRemoteDataSource,
|
||||
) {
|
||||
suspend fun getForStop(id: String): List<StopTimeDated> {
|
||||
return local
|
||||
.getAtStop(id)
|
||||
.ifEmpty { remote.getAtStop(id) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.androidMultiplatformLibrary)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
android {
|
||||
namespace = "moe.lava.banksia.core.data"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
||||
}
|
||||
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package moe.lava.banksia.core.data.repositories
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
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,
|
||||
private val remote: RouteRemoteDataSource,
|
||||
) : RouteRepository {
|
||||
private val mutex = Mutex()
|
||||
override suspend fun getAll() = mutex.withLock {
|
||||
local
|
||||
.getAll()
|
||||
.map { it.asModel() }
|
||||
.ifEmpty {
|
||||
remote
|
||||
.getAll()
|
||||
.also { local.save(*it.toTypedArray()) }
|
||||
}
|
||||
}
|
||||
|
||||
private val tripRouteMap = mutableMapOf<Long, Route>()
|
||||
|
||||
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 {
|
||||
local.save(it)
|
||||
tripRouteMap[patternId] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package moe.lava.banksia.core.data.repositories
|
||||
|
||||
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,
|
||||
private val remote: StopRemoteDataSource,
|
||||
) : StopRepository {
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) }
|
||||
override suspend fun getByRoute(id: String) = mutex.withLock {
|
||||
local
|
||||
.getByRoute(id)
|
||||
.map { it.asModel() }
|
||||
.ifEmpty { null }
|
||||
?: remote.getByRoute(id)
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package moe.lava.banksia.core.data.sources.route
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.get
|
||||
import moe.lava.banksia.core.model.Route
|
||||
|
||||
internal class RouteRemoteDataSource(val client: HttpClient) {
|
||||
suspend fun get(id: String) = client.get("routes/${id}").body<Route>()
|
||||
suspend fun getByPattern(patternId: Long) = client.get("routes/by_pattern/${patternId}").body<Route>()
|
||||
suspend fun getAll() = client.get("routes").body<List<Route>>()
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package moe.lava.banksia.core.data.repositories
|
||||
|
||||
import moe.lava.banksia.core.model.Route
|
||||
|
||||
interface RouteRepository {
|
||||
suspend fun get(id: String): Route?
|
||||
suspend fun getByPattern(patternId: Long): Route?
|
||||
suspend fun getAll(): List<Route>
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package moe.lava.banksia.core.data.repositories
|
||||
|
||||
import moe.lava.banksia.core.model.Stop
|
||||
|
||||
interface StopRepository {
|
||||
suspend fun get(id: String): Stop
|
||||
suspend fun getByRoute(id: String): List<Stop>
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package moe.lava.banksia.core.data
|
||||
|
||||
import org.koin.dsl.module
|
||||
|
||||
internal actual val platformModule = module {
|
||||
|
||||
}
|
||||
|
|
@ -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<Context>().applicationContext
|
||||
val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "${DBNAME}.db")
|
||||
BanksiaDatabase(driver)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package moe.lava.banksia.core.sqld
|
||||
|
||||
internal const val DBNAME = "timetable"
|
||||
|
||||
expect class DatabaseManager() {
|
||||
val database: BanksiaDatabase
|
||||
}
|
||||
|
|
@ -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<DatabaseManager>().database }
|
||||
factory { get<BanksiaDatabase>().routeQueries }
|
||||
factory { get<BanksiaDatabase>().serviceQueries }
|
||||
factory { get<BanksiaDatabase>().serviceExceptionQueries }
|
||||
factory { get<BanksiaDatabase>().shapeQueries }
|
||||
factory { get<BanksiaDatabase>().stopQueries }
|
||||
factory { get<BanksiaDatabase>().stoppingPatternQueries }
|
||||
factory { get<BanksiaDatabase>().stopTimeQueries }
|
||||
factory { get<BanksiaDatabase>().tripQueries }
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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(),
|
||||
)
|
||||
|
|
@ -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(),
|
||||
)
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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(),
|
||||
)
|
||||
|
|
@ -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 <T: TimeType> DbStoppingPattern.asModel(stoptimes: List<StopTime<T>>) = 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,
|
||||
)
|
||||
|
|
@ -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(),
|
||||
)
|
||||
|
|
@ -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 ?;
|
||||
|
|
@ -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 ?;
|
||||
|
|
@ -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 ?;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
CREATE TABLE Shape (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
path BLOB NOT NULL
|
||||
);
|
||||
|
||||
insert:
|
||||
INSERT INTO Shape VALUES ?;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 ?;
|
||||
Binary file not shown.
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
package moe.lava.banksia.core.endpoints
|
||||
|
||||
object Endpoint
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package moe.lava.banksia.core.model
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ServiceException(
|
||||
val serviceId: String,
|
||||
val date: LocalDate,
|
||||
val type: Int,
|
||||
)
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package moe.lava.banksia.core.model
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class StopTime<T: TimeType>(
|
||||
val patternId: Long,
|
||||
val stopId: String,
|
||||
val time: T,
|
||||
val pickupType: Int,
|
||||
val dropOffType: Int,
|
||||
) {
|
||||
typealias Dated = StopTime<TimeType.Dated>
|
||||
typealias Undated = StopTime<TimeType.Undated>
|
||||
}
|
||||
|
||||
@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<TimeType.Undated>.atDate(date: LocalDate) = StopTime(
|
||||
patternId = patternId,
|
||||
stopId = stopId,
|
||||
time = time.atDate(date),
|
||||
pickupType = pickupType,
|
||||
dropOffType = dropOffType,
|
||||
)
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package moe.lava.banksia.core.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class StoppingPattern<T: TimeType>(
|
||||
val id: Long,
|
||||
val routeId: String,
|
||||
val shapeId: String,
|
||||
val headsign: String,
|
||||
val wheelchairAccessible: Boolean,
|
||||
val stoptimes: List<StopTime<T>>,
|
||||
) {
|
||||
typealias Dated = StoppingPattern<TimeType.Dated>
|
||||
typealias Undated = StoppingPattern<TimeType.Undated>
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package moe.lava.banksia.core.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Trip<T: TimeType>(
|
||||
val id: String,
|
||||
val pattern: StoppingPattern<T>,
|
||||
val service: Service,
|
||||
val directionId: Int,
|
||||
val blockId: String?,
|
||||
) {
|
||||
typealias Dated = Trip<TimeType.Dated>
|
||||
typealias Undated = Trip<TimeType.Undated>
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.androidMultiplatformLibrary)
|
||||
alias(libs.plugins.ksp)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
android {
|
||||
namespace = "moe.lava.banksia.core.stoptime"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
||||
freeCompilerArgs.add("-Xexpect-actual-classes")
|
||||
}
|
||||
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
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.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)
|
||||
}
|
||||
jvmMain.dependencies {
|
||||
implementation(libs.koin.ktor)
|
||||
implementation(libs.ktor.server.core)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ExtendedStopTime> {
|
||||
return client.get(Endpoint.stopTimeByStop(stopId)) {
|
||||
parameter("date", date)
|
||||
}.body<List<ExtendedStopTime>>()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<List<ExtendedStopTime>>
|
||||
}
|
||||
|
|
@ -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<StopTimeQueries>()
|
||||
|
||||
suspend fun getAtStop(stopId: String, date: LocalDate): List<ExtendedStopTime> {
|
||||
return withContext(context = Dispatchers.IO) {
|
||||
queries
|
||||
.getExtendedForStop(
|
||||
listOf(date.dayOfWeek).serialise().toLong(),
|
||||
date.toEpochDays(),
|
||||
stopId,
|
||||
)
|
||||
.executeAsList()
|
||||
.map { it.asModel(date) }
|
||||
.sortedBy { it.time.departure }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
package moe.lava.banksia.core.endpoints
|
||||
|
||||
fun Endpoint.stopTimeByStop(stopId: String) = "stoptimes/by_stop/${stopId}"
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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<StopTimeRepository>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,40 @@
|
|||
[versions]
|
||||
agp = "9.1.0"
|
||||
android-compileSdk = "37"
|
||||
android-compileSdk = "36"
|
||||
android-minSdk = "24"
|
||||
android-targetSdk = "37"
|
||||
androidx-activity= "1.13.0"
|
||||
androidx-lifecycle = "2.10.0"
|
||||
compose-multiplatform = "1.12.0-alpha02"
|
||||
android-targetSdk = "36"
|
||||
androidx-activityCompose = "1.12.4"
|
||||
androidx-appcompat = "1.7.0"
|
||||
androidx-constraintlayout = "2.2.1"
|
||||
androidx-core-ktx = "1.15.0"
|
||||
androidx-espresso-core = "3.6.1"
|
||||
androidx-lifecycle = "2.9.6"
|
||||
androidx-material = "1.12.0"
|
||||
androidx-test-junit = "1.2.1"
|
||||
compose-multiplatform = "1.11.0-alpha02"
|
||||
composeunstyled = "1.49.6"
|
||||
coroutines = "1.10.2"
|
||||
geo = "0.8.0"
|
||||
koin = "4.2.0"
|
||||
kotlin = "2.3.20"
|
||||
junit = "4.13.2"
|
||||
koin = "4.1.1"
|
||||
kotlin = "2.3.10"
|
||||
kotlinxDatetime = "0.7.1"
|
||||
kotlinxSerializationCsv = "0.2.18"
|
||||
kotlinxSerialization = "1.10.0"
|
||||
ksp = "2.3.4"
|
||||
ktor = "3.4.1"
|
||||
ktor = "3.4.0"
|
||||
logback = "1.5.32"
|
||||
maplibre = "0.12.1"
|
||||
material = "1.7.3"
|
||||
material3 = "1.11.0-alpha07"
|
||||
okio = "3.17.0"
|
||||
material3 = "1.11.0-alpha02"
|
||||
okio = "3.16.4"
|
||||
playServicesLocation = "21.3.0"
|
||||
sqlite = "2.6.2"
|
||||
room = "2.8.4"
|
||||
secretsGradlePlugin = "2.0.1"
|
||||
sqldelight = "2.3.2"
|
||||
wire = "6.1.0"
|
||||
wire = "5.5.0"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
|
||||
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||
compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "compose-multiplatform" }
|
||||
compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "compose-multiplatform" }
|
||||
compose-material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "material" }
|
||||
|
|
@ -40,11 +44,18 @@ compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "compose-mu
|
|||
compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "compose-multiplatform" }
|
||||
compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "compose-multiplatform" }
|
||||
composeunstyled = { module = "com.composables:composeunstyled", version.ref = "composeunstyled" }
|
||||
moko-geo = { module = "dev.icerock.moko:geo", version.ref = "geo" }
|
||||
moko-geo-compose = { module = "dev.icerock.moko:geo-compose", version.ref = "geo" }
|
||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
|
||||
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
|
||||
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
|
||||
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
|
||||
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }
|
||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
|
||||
|
|
@ -62,14 +73,14 @@ ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "k
|
|||
ktor-server-tests = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" }
|
||||
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||
maplibre-compose = { module = "org.maplibre.compose:maplibre-compose", version.ref = "maplibre" }
|
||||
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-common = { group = "androidx.room", name = "room-common", version.ref = "room" }
|
||||
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
||||
room-ktx = { group = "androidx.room", name = "room-ktx", 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 +93,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" }
|
||||
|
|
|
|||
|
|
@ -5,27 +5,15 @@ plugins {
|
|||
application
|
||||
}
|
||||
|
||||
group = "moe.lava.banksia.server"
|
||||
group = "moe.lava.banksia"
|
||||
version = "1.0.0"
|
||||
application {
|
||||
mainClass.set("moe.lava.banksia.server.ApplicationKt")
|
||||
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=${extra["io.ktor.development"] ?: "false"}")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-Xexplicit-backing-fields")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core)
|
||||
implementation(projects.core.data)
|
||||
implementation(projects.core.sqld)
|
||||
implementation(projects.core.stoptime)
|
||||
implementation(projects.server.gtfs)
|
||||
implementation(projects.server.gtfsRt)
|
||||
|
||||
implementation(projects.shared)
|
||||
implementation(libs.logback)
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.koin.ktor)
|
||||
|
|
@ -38,6 +26,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
plugins {
|
||||
alias(libs.plugins.kotlinJvm)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
||||
freeCompilerArgs.add("-Xexplicit-backing-fields")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core)
|
||||
implementation(libs.kotlinx.serialization.csv)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
implementation(libs.ktor.client.contentnegotiation)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
}
|
||||
|
|
@ -1,388 +0,0 @@
|
|||
package moe.lava.banksia.server.gtfs
|
||||
|
||||
import com.lightningkite.kotlinx.serialization.csv.CsvFormat
|
||||
import com.lightningkite.kotlinx.serialization.csv.StringDeferringConfig
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.prepareRequest
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.util.cio.writeChannel
|
||||
import io.ktor.util.logging.Logger
|
||||
import io.ktor.utils.io.copyAndClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.datetime.DayOfWeek
|
||||
import kotlinx.datetime.LocalDate
|
||||
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
|
||||
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
|
||||
import moe.lava.banksia.server.gtfs.structures.GtfsService
|
||||
import moe.lava.banksia.server.gtfs.structures.GtfsServiceException
|
||||
import moe.lava.banksia.server.gtfs.structures.GtfsShape
|
||||
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
|
||||
|
||||
private typealias StopWithSource = Pair<String, Stop>
|
||||
|
||||
sealed class GtfsData {
|
||||
data class RouteChunk(val routes: List<Route>) : GtfsData()
|
||||
data class ServiceChunk(val services: List<Service>) : GtfsData()
|
||||
data class ServiceExceptionChunk(val exceptions: List<ServiceException>) : GtfsData()
|
||||
data class ShapeChunk(val shapes: List<Shape>) : GtfsData()
|
||||
data class StopChunk(val stops: List<Stop>) : GtfsData()
|
||||
data class TripChunk(val trips: List<Trip.Undated>) : GtfsData()
|
||||
}
|
||||
|
||||
class GtfsParser(
|
||||
private val log: Logger,
|
||||
private val client: HttpClient,
|
||||
) {
|
||||
private val csv = CsvFormat(StringDeferringConfig(EmptySerializersModule()))
|
||||
private val datasetPath = File("/tmp/banksia", "dataset.zip")
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
suspend fun update(datasetUrl: String): Flow<GtfsData> {
|
||||
val parentDir = datasetPath.parentFile
|
||||
@Suppress("SimplifyBooleanWithConstants", "KotlinConstantConditions")
|
||||
if (parentDir.exists() && !Constants.devMode)
|
||||
parentDir.deleteRecursively()
|
||||
|
||||
parentDir.mkdirs()
|
||||
|
||||
log.info("fetching..")
|
||||
client.prepareRequest {
|
||||
url(datasetUrl)
|
||||
}.execute { resp ->
|
||||
if (!datasetPath.exists())
|
||||
resp.bodyAsChannel().copyAndClose(datasetPath.writeChannel())
|
||||
log.info("fetched!")
|
||||
}
|
||||
|
||||
log.info("extracting...")
|
||||
@Suppress("KotlinConstantConditions")
|
||||
val files = if (Constants.devMode) {
|
||||
datasetPath.parentFile
|
||||
.listFiles { it.isDirectory }
|
||||
.flatMap { d -> d.listFiles { f -> f.extension == "txt" }.toList() }
|
||||
.ifEmpty { extractAll(datasetPath) }
|
||||
// .filter { it.parentFile.name == "2" }
|
||||
} else {
|
||||
extractAll(datasetPath)
|
||||
}
|
||||
|
||||
log.info("parsing...")
|
||||
return parse(files)
|
||||
.onCompletion {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (!Constants.devMode) {
|
||||
parentDir.deleteRecursively()
|
||||
}
|
||||
|
||||
log.info("done!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun parse(files: List<File>) = flow {
|
||||
files
|
||||
.filter { it.name == "routes.txt" }
|
||||
.forEach { emit(GtfsData.RouteChunk(parseRoutes(it))) }
|
||||
|
||||
files
|
||||
.filter { it.name == "stops.txt" }
|
||||
.flatMap { parseStops(it) }
|
||||
.let { emit(GtfsData.StopChunk(fixupDuplicateStops(it))) }
|
||||
|
||||
files
|
||||
.filter { it.name == "shapes.txt" }
|
||||
.forEach { emit(GtfsData.ShapeChunk(parseShapes(it))) }
|
||||
|
||||
val services = files
|
||||
.filter { it.name == "calendar.txt" }
|
||||
.flatMap { fd ->
|
||||
parseServices(fd)
|
||||
.also { emit(GtfsData.ServiceChunk(it)) }
|
||||
}
|
||||
.associateBy { it.id }
|
||||
|
||||
files
|
||||
.filter { it.name == "calendar_dates.txt" }
|
||||
.forEach { emit(GtfsData.ServiceExceptionChunk(parseServiceExceptions(it))) }
|
||||
|
||||
val trips = files
|
||||
.filter { it.name == "trips.txt" }
|
||||
.flatMap { fd ->
|
||||
parseTrips(fd, services)
|
||||
}
|
||||
.associateBy { it.id }
|
||||
|
||||
files
|
||||
.filter { it.name == "stop_times.txt" }
|
||||
.forEach { fd ->
|
||||
log.info("parsing stop times for ${fd.parent}...")
|
||||
parseStopTimes(fd) { seq ->
|
||||
val times = ArrayList<Pair<String, StopTime.Undated>>(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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hashCalc(headsign: String, stops: List<StopTime.Undated>): 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<String, Trip.Undated>, times: ArrayList<Pair<String, StopTime.Undated>>) =
|
||||
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<GtfsRoute>()
|
||||
.map { with(it) {
|
||||
Route(
|
||||
id = route_id,
|
||||
type = RouteType.from(fd.parentFile.name.toInt()),
|
||||
number = route_short_name,
|
||||
name = route_long_name,
|
||||
)
|
||||
} }
|
||||
|
||||
private fun parseShapes(fd: File) =
|
||||
fd.parseCsv<GtfsShape>()
|
||||
.groupBy { it.shape_id }
|
||||
.map { (id, group) ->
|
||||
val points = group
|
||||
.sortedBy { it.shape_pt_sequence }
|
||||
.map { Point(it.shape_pt_lat, it.shape_pt_lon) }
|
||||
|
||||
Shape(id, points)
|
||||
}
|
||||
|
||||
private fun parseStops(fd: File): List<StopWithSource> =
|
||||
fd.parseCsv<GtfsStop>()
|
||||
.map { with(it) {
|
||||
fd.parentFile.name to Stop(
|
||||
id = stop_id,
|
||||
name = stop_name,
|
||||
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 },
|
||||
)
|
||||
} }
|
||||
|
||||
private inline fun parseStopTimes(fd: File, block: (Sequence<Pair<String, StopTime.Undated>>) -> Unit) =
|
||||
fd.parseCsvSequence<GtfsStopTime> { seq ->
|
||||
seq
|
||||
.map { with(it) {
|
||||
it.trip_id to StopTime(
|
||||
patternId = stop_sequence,
|
||||
stopId = stop_id,
|
||||
time = TimeType.Undated(
|
||||
arrival = GtfsStopTime.parseGtfsTime(arrival_time),
|
||||
departure = GtfsStopTime.parseGtfsTime(departure_time),
|
||||
),
|
||||
pickupType = pickup_type,
|
||||
dropOffType = drop_off_type,
|
||||
)
|
||||
} }
|
||||
.let { block(it) }
|
||||
}
|
||||
|
||||
private fun parseServices(fd: File) =
|
||||
fd.parseCsv<GtfsService>()
|
||||
.map { with(it) {
|
||||
val days = buildList {
|
||||
if (monday == 1) add(DayOfWeek.MONDAY)
|
||||
if (tuesday == 1) add(DayOfWeek.TUESDAY)
|
||||
if (wednesday == 1) add(DayOfWeek.WEDNESDAY)
|
||||
if (thursday == 1) add(DayOfWeek.THURSDAY)
|
||||
if (friday == 1) add(DayOfWeek.FRIDAY)
|
||||
if (saturday == 1) add(DayOfWeek.SATURDAY)
|
||||
if (sunday == 1) add(DayOfWeek.SUNDAY)
|
||||
}
|
||||
Service(
|
||||
id = "${fd.parentFile.name}_${service_id}",
|
||||
days = days,
|
||||
start = LocalDate.parse(start_date, LocalDate.Formats.ISO_BASIC),
|
||||
end = LocalDate.parse(end_date, LocalDate.Formats.ISO_BASIC),
|
||||
)
|
||||
} }
|
||||
|
||||
private fun parseServiceExceptions(fd: File) =
|
||||
fd.parseCsv<GtfsServiceException>()
|
||||
.map { with(it) {
|
||||
ServiceException(
|
||||
serviceId = "${fd.parentFile.name}_${service_id}",
|
||||
date = LocalDate.parse(date, LocalDate.Formats.ISO_BASIC),
|
||||
type = exception_type,
|
||||
)
|
||||
} }
|
||||
|
||||
private fun parseTrips(fd: File, services: Map<String, Service>) =
|
||||
fd.parseCsv<GtfsTrip>()
|
||||
.map { with(it) {
|
||||
Trip.Undated(
|
||||
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 },
|
||||
)
|
||||
} }
|
||||
|
||||
private fun extract(fd: File): List<File> {
|
||||
val outputs = mutableListOf<File>()
|
||||
ZipFile(fd).use { zip ->
|
||||
for (entry in zip.entries()) {
|
||||
zip.getInputStream(entry).use { input ->
|
||||
val out = File(fd.parentFile, entry.name)
|
||||
out.parentFile.mkdirs()
|
||||
out.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
outputs.add(out)
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputs
|
||||
}
|
||||
|
||||
private fun extractAll(fd: File) = extract(fd).flatMap(::extract)
|
||||
|
||||
private inline fun <reified T> File.parseCsv(): List<T> = this
|
||||
.readText()
|
||||
.replace("\uFEFF", "") // remove bom
|
||||
.replace("\r\n", "\n") // crlf -> lf
|
||||
.let { csv.decodeFromString(it) }
|
||||
|
||||
private inline fun <reified T> File.parseCsvSequence(block: (Sequence<T>) -> Unit) = this
|
||||
.bufferedReader()
|
||||
.use { reader ->
|
||||
val iter = object : CharIterator() {
|
||||
var next: Char? = null
|
||||
override fun nextChar(): Char {
|
||||
if (!hasNext()) {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
val ret = next!!
|
||||
next = null
|
||||
return ret
|
||||
}
|
||||
override fun hasNext(): Boolean {
|
||||
if (next == null) {
|
||||
do {
|
||||
next = null
|
||||
val new = reader.read()
|
||||
if (new != -1) {
|
||||
next = new.toChar()
|
||||
}
|
||||
} while (next == '\uFEFF' || next == '\r')
|
||||
}
|
||||
return next != null
|
||||
}
|
||||
}
|
||||
block(csv.decodeToSequence(iter, csv.serializersModule.serializer()))
|
||||
}
|
||||
|
||||
// Type priority used to resolve duplicates, preferring the first one in the chain
|
||||
private val typePriorityRanking = listOf(
|
||||
RouteType.MetroTrain,
|
||||
RouteType.RegionalTrain,
|
||||
RouteType.MetroTram,
|
||||
RouteType.MetroBus,
|
||||
RouteType.RegionalBus,
|
||||
RouteType.SkyBus,
|
||||
).map { it.value.toString() }
|
||||
|
||||
@Suppress("LoggingStringTemplateAsArgument") // ?
|
||||
private fun fixupDuplicateStops(stops: List<StopWithSource>): List<Stop> {
|
||||
return stops
|
||||
.groupBy { (_, stops) -> stops.id }
|
||||
.map { (id, stops) ->
|
||||
// Just return it if no duplicate
|
||||
if (stops.size == 1) return@map stops[0].second
|
||||
|
||||
// Just return the first one if all the stops' data match
|
||||
if (stops.withIndex().all { (idx, stop) -> idx == 0 || stop.second == stops[idx - 1].second })
|
||||
return@map stops[0].second
|
||||
|
||||
// Find first stop ordered by the types
|
||||
val res = typePriorityRanking
|
||||
.firstNotNullOfOrNull { type ->
|
||||
stops.find { it.first == type }
|
||||
}
|
||||
|
||||
val (_, stop) = if (res == null) {
|
||||
log.warn("Cannot resolve duplicate stop ${id}, using first one")
|
||||
stops.forEach { (type, stop) -> log.warn(" - ($type): $stop") }
|
||||
stops[0]
|
||||
} else {
|
||||
log.debug("Resolving $id for type ${res.first}")
|
||||
stops.forEach { (type, stop) -> log.debug("${if (res.first == type) "*" else " "} - ($type): $stop") }
|
||||
res
|
||||
}
|
||||
|
||||
stop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package moe.lava.banksia.server.gtfs.structures
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
internal data class GtfsServiceException(
|
||||
val service_id: String,
|
||||
val date: String,
|
||||
val exception_type: Int,
|
||||
)
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
plugins {
|
||||
alias(libs.plugins.kotlinJvm)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.wire)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
||||
freeCompilerArgs.add("-Xexplicit-backing-fields")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core)
|
||||
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)
|
||||
}
|
||||
|
||||
wire {
|
||||
sourcePath {
|
||||
srcDir("src/main/proto")
|
||||
}
|
||||
kotlin {}
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
package moe.lava.banksia.server.gtfsrt
|
||||
|
||||
import com.google.transit.realtime.FeedMessage
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import moe.lava.banksia.core.util.log
|
||||
import java.io.File
|
||||
import kotlin.time.Instant
|
||||
|
||||
private const val BASE_DIR = "./data/gtfsr-archive/"
|
||||
|
||||
internal class GtfsrtArchiver {
|
||||
private var started = false
|
||||
|
||||
suspend fun start(flow: SharedFlow<Pair<String, ByteArray>>) {
|
||||
if (started) {
|
||||
log("GtfsrtArchiver", "Tried to start when already started")
|
||||
return
|
||||
}
|
||||
started = true
|
||||
coroutineScope {
|
||||
launch { compressJob() }
|
||||
|
||||
flow.collect { (type, rawData) ->
|
||||
val data = try {
|
||||
FeedMessage.ADAPTER.decode(rawData)
|
||||
} catch (e: Throwable) {
|
||||
log("gtfsr $type", "Failed to parse proto: $e")
|
||||
return@collect
|
||||
}
|
||||
val timestamp = data.header_.timestamp
|
||||
?: return@collect log("gtfsr $type", "Failed to read proto timestamp")
|
||||
|
||||
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 target = File(currentParent, "${timestamp}.proto")
|
||||
|
||||
if (previousParent.isDirectory) {
|
||||
enqueueCompression(previousParent)
|
||||
if (prevWeek != nextWeek) {
|
||||
enqueueCompression(previousParent.parentFile)
|
||||
}
|
||||
}
|
||||
|
||||
if (!target.exists()) {
|
||||
try {
|
||||
if (!target.parentFile.isDirectory) {
|
||||
target.parentFile.mkdirs()
|
||||
}
|
||||
target.writeBytes(rawData)
|
||||
} catch (e: Throwable) {
|
||||
log("gtfsr $type", "Failed to write ${target}: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val cqueue = mutableSetOf<File>()
|
||||
private val ignore = mutableSetOf<File>()
|
||||
private val cmut = Mutex()
|
||||
private suspend fun enqueueCompression(fd: File) {
|
||||
cmut.withLock { cqueue.add(fd) }
|
||||
}
|
||||
|
||||
private suspend fun compressJob() {
|
||||
while(true) {
|
||||
while(true) {
|
||||
val next = cmut.withLock { cqueue.firstOrNull() }
|
||||
?: break
|
||||
if (!next.isDirectory) {
|
||||
cmut.withLock { cqueue.remove(next) }
|
||||
continue
|
||||
}
|
||||
if (next in ignore) continue
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val proc = ProcessBuilder(
|
||||
"tar", "-acf",
|
||||
"${next.absolutePath}.tar.zst",
|
||||
next.absolutePath
|
||||
).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 {
|
||||
log("CompressJob", "Failed to delete $next")
|
||||
ignore.add(next)
|
||||
}
|
||||
} else {
|
||||
val msg = proc.errorStream.readAllBytes().decodeToString()
|
||||
log("CompressJob", "Failed to delete $next (exit code $exitCode")
|
||||
log("CompressJob", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(30000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
package moe.lava.banksia.server.gtfsrt
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.client.statement.readRawBytes
|
||||
import io.ktor.http.isSuccess
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.lava.banksia.core.Constants
|
||||
import moe.lava.banksia.core.util.LogScope
|
||||
import moe.lava.banksia.core.util.log
|
||||
|
||||
private val types = arrayOf(
|
||||
"metro/trip-updates",
|
||||
"metro/vehicle-positions",
|
||||
"metro/service-alerts",
|
||||
"tram/trip-updates",
|
||||
"tram/vehicle-positions",
|
||||
"tram/service-alerts",
|
||||
"bus/trip-updates",
|
||||
"bus/vehicle-positions",
|
||||
"vline/trip-updates",
|
||||
"vline/vehicle-positions",
|
||||
)
|
||||
|
||||
class GtfsrtService(
|
||||
private val client: HttpClient,
|
||||
) {
|
||||
private val archiver = GtfsrtArchiver()
|
||||
private var started = false
|
||||
|
||||
internal val rawMessages: SharedFlow<Pair<String, ByteArray>>
|
||||
field = MutableSharedFlow<Pair<String, ByteArray>>()
|
||||
|
||||
fun start(
|
||||
scope: CoroutineScope,
|
||||
enableArchiving: Boolean = false,
|
||||
) {
|
||||
if (started) {
|
||||
log("GtfsrtService", "Tried to start when already started")
|
||||
return
|
||||
}
|
||||
|
||||
if (enableArchiving) {
|
||||
scope.launch { archiver.start(rawMessages) }
|
||||
}
|
||||
|
||||
scope.launch { fetch() }
|
||||
}
|
||||
|
||||
private suspend fun fetch() {
|
||||
coroutineScope {
|
||||
types.map { type ->
|
||||
launch(context = Dispatchers.IO) {
|
||||
val logger = LogScope("gtfsr $type")
|
||||
try {
|
||||
val res = client.get {
|
||||
url("https://api.opendata.transport.vic.gov.au/opendata/public-transport/gtfs/realtime/v1/${type}")
|
||||
header("KeyId", Constants.opendataKey)
|
||||
}
|
||||
if (!res.status.isSuccess()) {
|
||||
logger.log("${res.status} | ${res.bodyAsText()}")
|
||||
} else {
|
||||
val bytes = res.readRawBytes()
|
||||
rawMessages.emit(type to bytes)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logger.log("$e")
|
||||
logger.log(e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
|
||||
delay(10000)
|
||||
fetch()
|
||||
}
|
||||
}
|
||||
|
|
@ -15,59 +15,47 @@ import io.ktor.server.routing.routing
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
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 kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.todayIn
|
||||
import moe.lava.banksia.Constants
|
||||
import moe.lava.banksia.di.CommonModules
|
||||
import moe.lava.banksia.model.atDate
|
||||
import moe.lava.banksia.room.dao.RouteDao
|
||||
import moe.lava.banksia.room.dao.StopDao
|
||||
import moe.lava.banksia.room.dao.StopTimeDao
|
||||
import moe.lava.banksia.room.dao.VersionMetadataDao
|
||||
import moe.lava.banksia.server.di.ServerModules
|
||||
import moe.lava.banksia.server.gtfsrt.GtfsrtService
|
||||
import moe.lava.banksia.server.routes.stopTimeRoutes
|
||||
import moe.lava.banksia.server.gtfs.GtfsHandler
|
||||
import moe.lava.banksia.server.gtfsr.GtfsrService
|
||||
import moe.lava.banksia.util.serialise
|
||||
import org.koin.dsl.module
|
||||
import org.koin.ktor.ext.get
|
||||
import org.koin.ktor.ext.inject
|
||||
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()
|
||||
}
|
||||
install(Koin) {
|
||||
modules(module { single { log } })
|
||||
modules(ServerModules)
|
||||
modules(CommonModules, ServerModules)
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
launch { get<GtfsrtService>().start(this, !Constants.devMode) }
|
||||
if (!Constants.devMode) {
|
||||
val gtfsr by inject<GtfsrService>()
|
||||
launch { gtfsr.start() }
|
||||
}
|
||||
|
||||
routing {
|
||||
stopTimeRoutes()
|
||||
|
||||
if (Constants.devMode) {
|
||||
get("/fixup") {
|
||||
call.respondText("received")
|
||||
get<GtfsDataFixer>().addParentsToStops()
|
||||
}
|
||||
}
|
||||
get("/manage/fixup") {
|
||||
val key = call.parameters["key"]
|
||||
if (key != Constants.updateKey) {
|
||||
call.respond(HttpStatusCode.Forbidden)
|
||||
return@get
|
||||
}
|
||||
|
||||
call.respondText("fixing")
|
||||
launch(context = Dispatchers.IO) {
|
||||
get<GtfsDataFixer>().addParentsToStops()
|
||||
}
|
||||
}
|
||||
get("/manage/update") {
|
||||
get("/update") {
|
||||
val key = call.parameters["key"]
|
||||
if (key != Constants.updateKey) {
|
||||
call.respond(HttpStatusCode.Forbidden)
|
||||
|
|
@ -79,14 +67,30 @@ fun Application.module() {
|
|||
?: "https://opendata.transport.vic.gov.au/dataset/3f4e292e-7f8a-4ffe-831f-1953be0fe448/resource/${datasetUuid}/download/gtfs.zip"
|
||||
call.respondText("received")
|
||||
launch(context = Dispatchers.IO) {
|
||||
get<GtfsImporter>().import(datasetUrl)
|
||||
get<GtfsDataFixer>().addParentsToStops()
|
||||
val handler by inject<GtfsHandler>()
|
||||
handler.update(datasetUrl)
|
||||
}
|
||||
}
|
||||
|
||||
get("/metadata/{type?}") {
|
||||
val dao by inject<VersionMetadataDao>()
|
||||
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<RouteQueries>().getAll().executeAsList()
|
||||
inject<RouteDao>().value.getAll()
|
||||
}
|
||||
val res = routes.map { it.asModel() }
|
||||
call.respond(res)
|
||||
|
|
@ -94,17 +98,16 @@ fun Application.module() {
|
|||
get("/routes/{route_id}") {
|
||||
val routeId = call.parameters["route_id"]!!
|
||||
val route = withContext(context = Dispatchers.IO) {
|
||||
get<RouteQueries>().get(routeId).executeAsOneOrNull()
|
||||
inject<RouteDao>().value.get(routeId)
|
||||
}
|
||||
if (route != null) {
|
||||
if (route != null)
|
||||
call.respond(route.asModel())
|
||||
} else {
|
||||
else
|
||||
call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
get("/stops") {
|
||||
val routes = withContext(context = Dispatchers.IO) {
|
||||
get<StopQueries>().getAll().executeAsList()
|
||||
inject<StopDao>().value.getAll()
|
||||
}
|
||||
val res = routes.map { it.asModel() }
|
||||
call.respond(res)
|
||||
|
|
@ -112,26 +115,56 @@ fun Application.module() {
|
|||
get("/stops/{stop_id}") {
|
||||
val stopId = call.parameters["stop_id"]!!
|
||||
val stop = withContext(context = Dispatchers.IO) {
|
||||
get<StopQueries>().get(stopId).executeAsOneOrNull()
|
||||
inject<StopDao>().value.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 useParent = call.queryParameters["parent"] in listOf("true", "1")
|
||||
val stops = withContext(Dispatchers.IO) {
|
||||
val queries = get<StopQueries>()
|
||||
if (useParent) {
|
||||
queries.getParentsByRoute(routeId).executeAsList()
|
||||
} else {
|
||||
queries.getByRoute(routeId).executeAsList()
|
||||
}
|
||||
val routeDao by inject<RouteDao>()
|
||||
if (useParent)
|
||||
routeDao.stopsParent(routeId)
|
||||
else
|
||||
routeDao.stops(routeId)
|
||||
}
|
||||
call.respond(stops.map { it.asModel() })
|
||||
// val stops = withContext(Dispatchers.IO) {
|
||||
// val stopDao by inject<StopDao>()
|
||||
// val stopTimeDao by inject<StopTimeDao>()
|
||||
// val tripDao by inject<TripDao>()
|
||||
//
|
||||
// tripDao.getByRoute(routeId)
|
||||
// .map { it.id }
|
||||
// .let { stopTimeDao.get(it) }
|
||||
// .flatMap { it.asModel().stopInfos }
|
||||
// .map { it.stopId }
|
||||
// .let { stopDao.get(it) }
|
||||
// .map { it.asModel() }
|
||||
// }
|
||||
// call.respond(stops)
|
||||
|
||||
}
|
||||
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) {
|
||||
inject<StopTimeDao>().value
|
||||
.getForStopDated(
|
||||
stopId,
|
||||
listOf(date.dayOfWeek).serialise(),
|
||||
date.toEpochDays().toInt(),
|
||||
)
|
||||
.map { it.asModel().atDate(date) }
|
||||
.sortedBy { it.departureTime }
|
||||
}
|
||||
call.respond(times)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
package moe.lava.banksia.server
|
||||
|
||||
import moe.lava.banksia.core.sqld.BanksiaDatabase
|
||||
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,
|
||||
) {
|
||||
fun addParentsToStops() {
|
||||
val queries = database.stopQueries
|
||||
val stops = queries.getAllParentless().executeAsList()
|
||||
stops
|
||||
.groupBy { it.name.split("/")[0] }
|
||||
.filter { (_, stops) -> stops.size > 1 }
|
||||
.forEach { (name, stops) ->
|
||||
val avgLat = stops.map { it.lat }.average()
|
||||
val avgLng = stops.map { it.lng }.average()
|
||||
val hash = name.sha256().substring(0, 7)
|
||||
val parentId = "bsia:df1:$hash"
|
||||
val parent = DbStop(
|
||||
id = parentId,
|
||||
name = name,
|
||||
lat = avgLat,
|
||||
lng = avgLng,
|
||||
parent = null,
|
||||
hasWheelChairBoarding = if (stops.all { it.hasWheelChairBoarding == 1L }) 1L else 0L,
|
||||
level = "",
|
||||
platformCode = "",
|
||||
)
|
||||
log("datafixer", "inserting ${parentId} for ${stops.size} children")
|
||||
queries.transaction {
|
||||
queries.insert(parent)
|
||||
queries.updateParents(parentId, stops.map { it.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.sha256() =
|
||||
MessageDigest
|
||||
.getInstance("SHA-256")
|
||||
.digest(this.toByteArray())
|
||||
.joinToString("") { "%02x".format(it) }
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
package moe.lava.banksia.server
|
||||
|
||||
import io.ktor.util.logging.Logger
|
||||
import moe.lava.banksia.core.model.Route
|
||||
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.Trip
|
||||
import moe.lava.banksia.core.sqld.DatabaseManager
|
||||
import moe.lava.banksia.core.sqld.mappers.asDb
|
||||
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,
|
||||
private val dbm: DatabaseManager,
|
||||
private val log: Logger,
|
||||
) {
|
||||
suspend fun import(url: String, date: Long = Clock.System.now().epochSeconds) {
|
||||
val (database, close) = dbm.makeAlt()
|
||||
|
||||
parser.update(url).collect { chunk ->
|
||||
when (chunk) {
|
||||
is GtfsData.RouteChunk -> database.addRoutes(chunk.routes)
|
||||
is GtfsData.ServiceChunk -> database.addServices(chunk.services)
|
||||
is GtfsData.ServiceExceptionChunk -> database.addServiceExceptions(chunk.exceptions)
|
||||
is GtfsData.ShapeChunk -> database.addShapes(chunk.shapes)
|
||||
is GtfsData.StopChunk -> database.addStops(chunk.stops)
|
||||
is GtfsData.TripChunk -> database.addTrips(chunk.trips)
|
||||
}
|
||||
}
|
||||
|
||||
close()
|
||||
dbm.swap()
|
||||
}
|
||||
|
||||
private fun Database.addRoutes(routes: List<Route>) {
|
||||
log.info("inserting routes...")
|
||||
routeQueries.transaction {
|
||||
routes.forEach {
|
||||
routeQueries.insert(it.asDb())
|
||||
}
|
||||
}
|
||||
log.info("done")
|
||||
}
|
||||
|
||||
private fun Database.addServices(services: List<Service>) {
|
||||
log.info("inserting services...")
|
||||
serviceQueries.transaction {
|
||||
services.forEach {
|
||||
serviceQueries.insert(it.asDb())
|
||||
}
|
||||
}
|
||||
log.info("done")
|
||||
}
|
||||
|
||||
private fun Database.addServiceExceptions(exceptions: List<ServiceException>) {
|
||||
log.info("inserting exceptions...")
|
||||
serviceExceptionQueries.transaction {
|
||||
exceptions.forEach {
|
||||
serviceExceptionQueries.insert(it.asDb())
|
||||
}
|
||||
}
|
||||
log.info("done")
|
||||
}
|
||||
|
||||
private fun Database.addShapes(shapes: List<Shape>) {
|
||||
log.info("inserting shapes...")
|
||||
shapeQueries.transaction {
|
||||
shapes.forEach {
|
||||
shapeQueries.insert(it.asDb())
|
||||
}
|
||||
}
|
||||
log.info("done")
|
||||
}
|
||||
|
||||
private fun Database.addStops(stops: List<Stop>) {
|
||||
log.info("inserting stops...")
|
||||
stops
|
||||
.groupBy { it.id }
|
||||
.forEach { (id, gstops) ->
|
||||
if (gstops.size > 1) {
|
||||
if (gstops.withIndex().any { (i, stop) -> i != 0 && stop != gstops[i - 1] }) {
|
||||
gstops.forEach {
|
||||
log.warn("duplicate $id: $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopQueries.transaction {
|
||||
stops.forEach {
|
||||
stopQueries.insert(it.asDb())
|
||||
}
|
||||
}
|
||||
log.info("done")
|
||||
}
|
||||
|
||||
private fun Database.addTrips(trips: List<Trip.Undated>) {
|
||||
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())
|
||||
}
|
||||
}
|
||||
log.info("done")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,13 @@
|
|||
package moe.lava.banksia.server.di
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import moe.lava.banksia.core.data.dataDiModule
|
||||
import moe.lava.banksia.server.GtfsDataFixer
|
||||
import moe.lava.banksia.server.GtfsImporter
|
||||
import moe.lava.banksia.server.gtfs.GtfsParser
|
||||
import moe.lava.banksia.server.gtfsrt.GtfsrtService
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import moe.lava.banksia.server.gtfs.GtfsHandler
|
||||
import moe.lava.banksia.server.gtfsr.GtfsrService
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val ServerModules = module {
|
||||
includes(dataDiModule)
|
||||
|
||||
single { HttpClient() }
|
||||
singleOf(::GtfsParser)
|
||||
singleOf(::GtfsrtService)
|
||||
|
||||
factoryOf(::GtfsDataFixer)
|
||||
factoryOf(::GtfsImporter)
|
||||
singleOf(::GtfsHandler)
|
||||
singleOf(::GtfsrService)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,336 @@
|
|||
package moe.lava.banksia.server.gtfs
|
||||
|
||||
import com.lightningkite.kotlinx.serialization.csv.CsvFormat
|
||||
import com.lightningkite.kotlinx.serialization.csv.StringDeferringConfig
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.prepareRequest
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.util.cio.writeChannel
|
||||
import io.ktor.util.logging.Logger
|
||||
import io.ktor.utils.io.copyAndClose
|
||||
import kotlinx.datetime.DayOfWeek
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.modules.EmptySerializersModule
|
||||
import kotlinx.serialization.serializer
|
||||
import moe.lava.banksia.Constants
|
||||
import moe.lava.banksia.model.Route
|
||||
import moe.lava.banksia.model.Service
|
||||
import moe.lava.banksia.model.Shape
|
||||
import moe.lava.banksia.model.Stop
|
||||
import moe.lava.banksia.model.StopTime
|
||||
import moe.lava.banksia.model.Trip
|
||||
import moe.lava.banksia.room.Database
|
||||
import moe.lava.banksia.room.converter.RouteTypeConverter
|
||||
import moe.lava.banksia.room.entity.asEntity
|
||||
import moe.lava.banksia.server.gtfs.structures.GtfsRoute
|
||||
import moe.lava.banksia.server.gtfs.structures.GtfsService
|
||||
import moe.lava.banksia.server.gtfs.structures.GtfsShape
|
||||
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 moe.lava.banksia.util.Point
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
class GtfsHandler(
|
||||
private val log: Logger,
|
||||
private val client: HttpClient,
|
||||
private val db: Database,
|
||||
) {
|
||||
private val csv = CsvFormat(StringDeferringConfig(EmptySerializersModule()))
|
||||
private val datasetPath = File("/tmp/banksia", "dataset.zip")
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
suspend fun update(datasetUrl: String, date: Long? = null) {
|
||||
val parentDir = datasetPath.parentFile
|
||||
@Suppress("SimplifyBooleanWithConstants", "KotlinConstantConditions")
|
||||
if (parentDir.exists() && !Constants.devMode)
|
||||
parentDir.deleteRecursively()
|
||||
|
||||
parentDir.mkdirs()
|
||||
|
||||
log.info("fetching..")
|
||||
client.prepareRequest {
|
||||
url(datasetUrl)
|
||||
}.execute { resp ->
|
||||
if (!datasetPath.exists())
|
||||
resp.bodyAsChannel().copyAndClose(datasetPath.writeChannel())
|
||||
log.info("fetched!")
|
||||
}
|
||||
|
||||
log.info("extracting...")
|
||||
@Suppress("KotlinConstantConditions")
|
||||
val files = if (Constants.devMode) {
|
||||
datasetPath.parentFile
|
||||
.listFiles { it.isDirectory }
|
||||
.flatMap { d -> d.listFiles { f -> f.extension == "txt" }.toList() }
|
||||
.ifEmpty { extractAll(datasetPath) }
|
||||
.filter { it.parentFile.name == "2" }
|
||||
} else {
|
||||
extractAll(datasetPath)
|
||||
}
|
||||
|
||||
addRoutes(files)
|
||||
addStops(files)
|
||||
addShapes(files)
|
||||
val services = addServices(files)
|
||||
val trips = addTrips(files, services.associateBy { it.id })
|
||||
addStopTimes(files, trips.associateBy { it.id })
|
||||
|
||||
updateMetadata(date ?: Clock.System.now().epochSeconds)
|
||||
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (!Constants.devMode) {
|
||||
parentDir.deleteRecursively()
|
||||
}
|
||||
|
||||
log.info("done!")
|
||||
}
|
||||
|
||||
private suspend fun updateMetadata(date: Long) {
|
||||
val dao = db.versionMetadataDao
|
||||
log.info("updating metadata...")
|
||||
dao.update(date, listOf("routes", "stops", "shapes", "trips", "stop_times"))
|
||||
}
|
||||
|
||||
private suspend fun addRoutes(files: List<File>) {
|
||||
val dao = db.routeDao
|
||||
log.info("parsing routes...")
|
||||
val routes = files
|
||||
.filter { it.name == "routes.txt" }
|
||||
.flatMap { fd -> parseRoutes(fd) }
|
||||
|
||||
log.info("inserting routes...")
|
||||
dao.deleteAll()
|
||||
dao.insertAll(*routes.map { it.asEntity() }.toTypedArray())
|
||||
}
|
||||
|
||||
private fun parseRoutes(fd: File) =
|
||||
fd.parseCsv<GtfsRoute>()
|
||||
.map { with(it) {
|
||||
Route(
|
||||
id = route_id,
|
||||
type = RouteTypeConverter.from(fd.parentFile.name.toInt()),
|
||||
number = route_short_name,
|
||||
name = route_long_name,
|
||||
)
|
||||
} }
|
||||
|
||||
private suspend fun addShapes(files: List<File>) {
|
||||
val dao = db.shapeDao
|
||||
log.info("parsing shapes...")
|
||||
val shapes = files
|
||||
.filter { it.name == "shapes.txt" }
|
||||
.flatMap { fd -> parseShapes(fd) }
|
||||
|
||||
log.info("inserting shapes...")
|
||||
dao.deleteAll()
|
||||
dao.insertAll(*shapes.map { it.asEntity() }.toTypedArray())
|
||||
}
|
||||
|
||||
private fun parseShapes(fd: File) =
|
||||
fd.parseCsv<GtfsShape>()
|
||||
.groupBy { it.shape_id }
|
||||
.map { (id, group) ->
|
||||
val points = group
|
||||
.sortedBy { it.shape_pt_sequence }
|
||||
.map { Point(it.shape_pt_lat, it.shape_pt_lon) }
|
||||
|
||||
Shape(id, points)
|
||||
}
|
||||
|
||||
private suspend fun addStops(files: List<File>) {
|
||||
val dao = db.stopDao
|
||||
log.info("parsing stops...")
|
||||
val stops = files
|
||||
.filter { it.name == "stops.txt" }
|
||||
.flatMap { fd -> parseStops(fd) }
|
||||
|
||||
log.info("inserting stops...")
|
||||
dao.deleteAll()
|
||||
stops
|
||||
.groupBy { it.id }
|
||||
.forEach { (id, gstops) ->
|
||||
if (gstops.size > 1) {
|
||||
if (gstops.withIndex().any { (i, stop) -> i != 0 && stop != gstops[i - 1] }) {
|
||||
gstops.forEach {
|
||||
log.info("duplicate $id: $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray())
|
||||
}
|
||||
|
||||
private fun parseStops(fd: File) =
|
||||
fd.parseCsv<GtfsStop>()
|
||||
.map { with(it) {
|
||||
Stop(
|
||||
id = stop_id,
|
||||
name = stop_name,
|
||||
pos = Point(stop_lat, stop_lon),
|
||||
parent = parent_station,
|
||||
hasWheelChairBoarding = wheelchair_boarding == "1",
|
||||
level = level_id,
|
||||
platformCode = platform_code,
|
||||
)
|
||||
} }
|
||||
|
||||
private suspend fun addStopTimes(files: List<File>, trips: Map<String, Trip>) {
|
||||
val dao = db.stopTimeDao
|
||||
dao.deleteAll()
|
||||
log.info("parsing stop times...")
|
||||
files
|
||||
.filter { it.name == "stop_times.txt" }
|
||||
.forEach { fd ->
|
||||
log.info("parsing stop times for ${fd.parent}...")
|
||||
parseStopTimes(fd, trips) { seq ->
|
||||
seq.chunked(1000000)
|
||||
.forEach { queue ->
|
||||
log.info("converting stop times (${queue.size}) for ${fd.parent}...")
|
||||
val conv = queue.map { it.asEntity() }.toTypedArray()
|
||||
log.info("inserting stop times (${conv.size}) for ${fd.parent}...")
|
||||
dao.insertOrReplaceAll(*conv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun parseStopTimes(fd: File, trips: Map<String, Trip>, block: (Sequence<StopTime>) -> Unit) =
|
||||
fd.parseCsvSequence<GtfsStopTime> { seq ->
|
||||
seq
|
||||
.map { with(it) {
|
||||
StopTime(
|
||||
tripId = trip_id,
|
||||
stopId = stop_id,
|
||||
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,
|
||||
)
|
||||
} }
|
||||
.let { block(it) }
|
||||
}
|
||||
|
||||
private suspend fun addServices(files: List<File>): List<Service> {
|
||||
val dao = db.serviceDao
|
||||
log.info("parsing services...")
|
||||
val services = files
|
||||
.filter { it.name == "calendar.txt" }
|
||||
.flatMap { fd -> parseServices(fd) }
|
||||
|
||||
log.info("inserting services...")
|
||||
dao.deleteAll()
|
||||
dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray())
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
private fun parseServices(fd: File) =
|
||||
fd.parseCsv<GtfsService>()
|
||||
.map { with(it) {
|
||||
val days = buildList {
|
||||
if (monday == 1) add(DayOfWeek.MONDAY)
|
||||
if (tuesday == 1) add(DayOfWeek.TUESDAY)
|
||||
if (wednesday == 1) add(DayOfWeek.WEDNESDAY)
|
||||
if (thursday == 1) add(DayOfWeek.THURSDAY)
|
||||
if (friday == 1) add(DayOfWeek.FRIDAY)
|
||||
if (saturday == 1) add(DayOfWeek.SATURDAY)
|
||||
if (sunday == 1) add(DayOfWeek.SUNDAY)
|
||||
}
|
||||
Service(
|
||||
id = service_id,
|
||||
days = days,
|
||||
start = LocalDate.parse(start_date, LocalDate.Formats.ISO_BASIC),
|
||||
end = LocalDate.parse(end_date, LocalDate.Formats.ISO_BASIC),
|
||||
)
|
||||
} }
|
||||
|
||||
private suspend fun addTrips(files: List<File>, services: Map<String, Service>): List<Trip> {
|
||||
val dao = db.tripDao
|
||||
log.info("parsing trips...")
|
||||
val trips = files
|
||||
.filter { it.name == "trips.txt" }
|
||||
.flatMap { fd -> parseTrips(fd, services) }
|
||||
|
||||
log.info("inserting trips...")
|
||||
dao.deleteAll()
|
||||
dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray())
|
||||
|
||||
return trips
|
||||
}
|
||||
|
||||
private fun parseTrips(fd: File, services: Map<String, Service>) =
|
||||
fd.parseCsv<GtfsTrip>()
|
||||
.map { with(it) {
|
||||
Trip(
|
||||
id = trip_id,
|
||||
routeId = route_id,
|
||||
service = services[service_id]!!,
|
||||
shapeId = shape_id.ifEmpty { null },
|
||||
tripHeadsign = trip_headsign,
|
||||
directionId = direction_id,
|
||||
blockId = block_id,
|
||||
wheelchairAccessible = wheelchair_accessible,
|
||||
)
|
||||
} }
|
||||
|
||||
private fun extract(fd: File): List<File> {
|
||||
val outputs = mutableListOf<File>()
|
||||
ZipFile(fd).use { zip ->
|
||||
for (entry in zip.entries()) {
|
||||
zip.getInputStream(entry).use { input ->
|
||||
val out = File(fd.parentFile, entry.name)
|
||||
out.parentFile.mkdirs()
|
||||
out.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
outputs.add(out)
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputs
|
||||
}
|
||||
|
||||
private fun extractAll(fd: File) = extract(fd).flatMap(::extract)
|
||||
|
||||
private inline fun <reified T> File.parseCsv(): List<T> = this
|
||||
.readText()
|
||||
.replace("\uFEFF", "") // remove bom
|
||||
.replace("\r\n", "\n") // crlf -> lf
|
||||
.let { csv.decodeFromString(it) }
|
||||
|
||||
private inline fun <reified T> File.parseCsvSequence(block: (Sequence<T>) -> Unit) = this
|
||||
.bufferedReader()
|
||||
.use { reader ->
|
||||
val iter = object : CharIterator() {
|
||||
var next: Char? = null
|
||||
override fun nextChar(): Char {
|
||||
if (!hasNext()) {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
val ret = next!!
|
||||
next = null
|
||||
return ret
|
||||
}
|
||||
override fun hasNext(): Boolean {
|
||||
if (next == null) {
|
||||
do {
|
||||
next = null
|
||||
val new = reader.read()
|
||||
if (new != -1) {
|
||||
next = new.toChar()
|
||||
}
|
||||
} while (next == '\uFEFF' || next == '\r')
|
||||
}
|
||||
return next != null
|
||||
}
|
||||
}
|
||||
block(csv.decodeToSequence(iter, csv.serializersModule.serializer()))
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
internal data class GtfsRoute(
|
||||
data class GtfsRoute(
|
||||
val route_id: String,
|
||||
val agency_id: String,
|
||||
val route_short_name: String,
|
||||
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
internal data class GtfsService(
|
||||
data class GtfsService(
|
||||
val service_id: String,
|
||||
val monday: Int,
|
||||
val tuesday: Int,
|
||||
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
internal data class GtfsShape(
|
||||
data class GtfsShape(
|
||||
val shape_id: String,
|
||||
val shape_pt_lat: Double,
|
||||
val shape_pt_lon: Double,
|
||||
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
internal data class GtfsStop(
|
||||
data class GtfsStop(
|
||||
val stop_id: String,
|
||||
val stop_name: String,
|
||||
val stop_lat: Double,
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
package moe.lava.banksia.server.gtfs.structures
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.lava.banksia.core.model.FutureTime
|
||||
import moe.lava.banksia.model.FutureTime
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
internal data class GtfsStopTime(
|
||||
data class GtfsStopTime(
|
||||
val trip_id: String,
|
||||
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,
|
||||
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
internal data class GtfsTrip(
|
||||
data class GtfsTrip(
|
||||
val route_id: String,
|
||||
val service_id: String,
|
||||
val trip_id: String,
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
package moe.lava.banksia.server.gtfsr
|
||||
|
||||
import com.google.transit.realtime.FeedMessage
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.client.statement.readRawBytes
|
||||
import io.ktor.http.isSuccess
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import moe.lava.banksia.Constants
|
||||
import moe.lava.banksia.util.LogScope
|
||||
import moe.lava.banksia.util.log
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
|
||||
private const val BASE_DIR = "./data/gtfsr-archive/"
|
||||
|
||||
class GtfsrService(private val client: HttpClient) {
|
||||
private var started = false
|
||||
private val latest = mutableMapOf<String, FeedMessage>()
|
||||
|
||||
fun latestFor(type: String) = latest[type]
|
||||
|
||||
private val iFlow = MutableSharedFlow<Pair<String, FeedMessage>>()
|
||||
val flow = iFlow.asSharedFlow()
|
||||
|
||||
companion object {
|
||||
val types = arrayOf(
|
||||
"metro/trip-updates",
|
||||
"metro/vehicle-positions",
|
||||
"metro/service-alerts",
|
||||
"tram/trip-updates",
|
||||
"tram/vehicle-positions",
|
||||
"tram/service-alerts",
|
||||
"bus/trip-updates",
|
||||
"bus/vehicle-positions",
|
||||
"vline/trip-updates",
|
||||
"vline/vehicle-positions",
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun start() {
|
||||
if (started) {
|
||||
log("GtfsrService", "Tried to start when already started")
|
||||
return
|
||||
}
|
||||
started = true
|
||||
coroutineScope {
|
||||
launch { compressJob() }
|
||||
|
||||
while (true) {
|
||||
val results = mutableMapOf<String, ByteArray>()
|
||||
types.map { type ->
|
||||
launch(context = Dispatchers.IO) {
|
||||
val logger = LogScope("gtfsr $type")
|
||||
try {
|
||||
val res = client.get {
|
||||
url("https://api.opendata.transport.vic.gov.au/opendata/public-transport/gtfs/realtime/v1/${type}")
|
||||
header("KeyId", Constants.opendataKey)
|
||||
}
|
||||
if (!res.status.isSuccess()) {
|
||||
logger.log("${res.status} | ${res.bodyAsText()}")
|
||||
} else {
|
||||
results[type] = res.readRawBytes()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logger.log("$e")
|
||||
logger.log(e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
|
||||
results.forEach { (type, data) ->
|
||||
val dec = try {
|
||||
FeedMessage.ADAPTER.decode(data)
|
||||
} catch (e: Throwable) {
|
||||
log("gtfsr $type", "Failed to parse proto: $e")
|
||||
return@forEach
|
||||
}
|
||||
val timestamp = dec.header_.timestamp
|
||||
?: return@forEach log("gtfsr $type", "Failed to read proto timestamp")
|
||||
|
||||
val time = Instant.ofEpochSecond(timestamp).atZone(ZoneId.systemDefault())
|
||||
|
||||
val base = File(BASE_DIR, type)
|
||||
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 (!target.exists()) {
|
||||
try {
|
||||
if (!target.parentFile.isDirectory) {
|
||||
target.parentFile.mkdirs()
|
||||
}
|
||||
target.writeBytes(data)
|
||||
} catch (e: Throwable) {
|
||||
log("gtfsr $type", "Failed to write ${target}: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(10000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val cqueue = mutableSetOf<File>()
|
||||
private val ignore = mutableSetOf<File>()
|
||||
private val cmut = Mutex()
|
||||
private suspend fun enqueueCompression(fd: File) {
|
||||
cmut.withLock { cqueue.add(fd) }
|
||||
}
|
||||
|
||||
private suspend fun compressJob() {
|
||||
while(true) {
|
||||
while(true) {
|
||||
val next = cmut.withLock { cqueue.firstOrNull() }
|
||||
?: break
|
||||
if (!next.isDirectory) {
|
||||
cmut.withLock { cqueue.remove(next) }
|
||||
continue
|
||||
}
|
||||
if (next in ignore) continue
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val proc = ProcessBuilder(
|
||||
"tar", "-acf",
|
||||
"${next.absolutePath}.tar.zst",
|
||||
next.absolutePath
|
||||
).start()
|
||||
val exitCode = proc.waitFor()
|
||||
if (exitCode == 0) {
|
||||
if (next.deleteRecursively()) {
|
||||
cmut.withLock { cqueue.remove(next) }
|
||||
} else {
|
||||
log("CompressJob", "Failed to delete $next")
|
||||
ignore.add(next)
|
||||
}
|
||||
} else {
|
||||
val msg = proc.errorStream.readAllBytes().decodeToString()
|
||||
log("CompressJob", "Failed to delete $next (exit code $exitCode")
|
||||
log("CompressJob", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(30000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="debug">
|
||||
<root level="trace">
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
|
|
|||
|
|
@ -32,13 +32,9 @@ dependencyResolutionManagement {
|
|||
}
|
||||
|
||||
include(":androidApp")
|
||||
include(":client")
|
||||
include(":server")
|
||||
include(":server:gtfs")
|
||||
include(":server:gtfs_rt")
|
||||
include(":core")
|
||||
include(":core:data")
|
||||
include(":core:stoptime")
|
||||
include(":core:sqld")
|
||||
include(":shared")
|
||||
include(":ui")
|
||||
include(":ui:maps")
|
||||
include(":ui:shared")
|
||||
|
|
|
|||
|
|
@ -4,11 +4,18 @@ plugins {
|
|||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.androidMultiplatformLibrary)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.room)
|
||||
alias(libs.plugins.wire)
|
||||
}
|
||||
|
||||
room {
|
||||
schemaDirectory("$projectDir/schemas")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
android {
|
||||
namespace = "moe.lava.banksia.core"
|
||||
namespace = "moe.lava.banksia.shared"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
compilerOptions {
|
||||
|
|
@ -40,9 +47,25 @@ 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)
|
||||
}
|
||||
|
||||
wire {
|
||||
sourcePath {
|
||||
srcDir("src/commonMain/proto")
|
||||
}
|
||||
kotlin {}
|
||||
}
|
||||
72
shared/schemas/moe.lava.banksia.room.Database/1.json
Normal file
72
shared/schemas/moe.lava.banksia.room.Database/1.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
315
shared/schemas/moe.lava.banksia.room.Database/2.json
Normal file
315
shared/schemas/moe.lava.banksia.room.Database/2.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
339
shared/schemas/moe.lava.banksia.room.Database/3.json
Normal file
339
shared/schemas/moe.lava.banksia.room.Database/3.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
368
shared/schemas/moe.lava.banksia.room.Database/4.json
Normal file
368
shared/schemas/moe.lava.banksia.room.Database/4.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
368
shared/schemas/moe.lava.banksia.room.Database/5.json
Normal file
368
shared/schemas/moe.lava.banksia.room.Database/5.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
368
shared/schemas/moe.lava.banksia.room.Database/6.json
Normal file
368
shared/schemas/moe.lava.banksia.room.Database/6.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
415
shared/schemas/moe.lava.banksia.room.Database/7.json
Normal file
415
shared/schemas/moe.lava.banksia.room.Database/7.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
426
shared/schemas/moe.lava.banksia.room.Database/8.json
Normal file
426
shared/schemas/moe.lava.banksia.room.Database/8.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
426
shared/schemas/moe.lava.banksia.room.Database/9.json
Normal file
426
shared/schemas/moe.lava.banksia.room.Database/9.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package moe.lava.banksia.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import moe.lava.banksia.room.Database
|
||||
import org.koin.core.parameter.ParametersHolder
|
||||
import org.koin.core.scope.Scope
|
||||
import org.koin.dsl.module
|
||||
|
||||
class AndroidDatabaseBuilder(val ctx: Context) : PlatformDatabaseBuilder {
|
||||
override fun getBuilder(): RoomDatabase.Builder<Database> {
|
||||
val appContext = ctx.applicationContext
|
||||
val dbFile = appContext.getDatabasePath("room.db")
|
||||
return Room.databaseBuilder<Database>(
|
||||
context = appContext,
|
||||
name = dbFile.absolutePath
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder =
|
||||
AndroidDatabaseBuilder(get())
|
||||
|
||||
internal actual val ExtPlatformModule = module { }
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.banksia.core.util
|
||||
package moe.lava.banksia.util
|
||||
|
||||
import android.util.Log
|
||||
|
||||
|
|
@ -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 = ""
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package moe.lava.banksia.server.gtfsrt
|
||||
package moe.lava.banksia.data.gtfsr
|
||||
|
||||
import com.google.transit.realtime.FeedMessage
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package moe.lava.banksia.server.gtfsrt
|
||||
package moe.lava.banksia.data.gtfsr
|
||||
|
||||
import com.google.transit.realtime.FeedMessage
|
||||
import moe.lava.banksia.core.util.Point
|
||||
import moe.lava.banksia.util.Point
|
||||
|
||||
class RealtimeVehiclePositions(data: FeedMessage) : GtfsRealtime(data) {
|
||||
private val positions = mutableMapOf<String, Point>()
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue