Author: Adam Pioterek <adam.pioterek@protonmail.ch>
optimised getting departures from db
app/src/main/java/ml/adamsprogs/bimba/CacheManager.kt | 2 app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt | 4 app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt | 24 app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt | 6 app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt | 4 app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt | 122 +
diff --git a/app/src/main/java/ml/adamsprogs/bimba/CacheManager.kt b/app/src/main/java/ml/adamsprogs/bimba/CacheManager.kt index a4271b84969e54773e0debddae5e6354dd73bb02..53d2b0f13580126382cd493b89c763f0ea830674 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/CacheManager.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/CacheManager.kt @@ -107,7 +107,7 @@ cacheHits[key] = hits + 1 return cache[key] } - fun recreate(stopDeparturesByPlates: HashSet<Plate>) { + fun recreate(stopDeparturesByPlates: Set<Plate>) { stopDeparturesByPlates.forEach { cache[key(it)] = it } } diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt index 2ed04860296ff1106e3e912c14de22bc2c2c50e3..49dcbb5db4881a919b5254e40f1c2e94c70bbdd9 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt @@ -176,7 +176,7 @@ } private fun getStops() { timetable = Timetable.getTimetable(this) - stops = timetable.getStops() + stops = timetable.getStops() as ArrayList<StopSuggestion> } private fun prepareOnDownloadListener() { @@ -256,7 +256,7 @@ else -> getString(R.string.error_try_later) } if (result == TimetableDownloader.RESULT_DOWNLOADED) { timetable.refresh(context) - stops = timetable.getStops() + stops = timetable.getStops() as ArrayList<StopSuggestion> } Snackbar.make(findViewById(R.id.drawer_layout), message, Snackbar.LENGTH_LONG).show() } diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt index edd6a40349c9a34d0bb79d63b7d7a9a379e853da..cf5957bb9d98ae49ece7663ad2923fe1bfcb3e21 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt @@ -12,6 +12,7 @@ import android.support.v7.widget.* import android.support.v4.app.* import android.support.v4.view.* import android.support.v4.content.res.ResourcesCompat +import android.util.Log import ml.adamsprogs.bimba.models.* import ml.adamsprogs.bimba.* @@ -82,9 +83,12 @@ sectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager, null) thread { if (sourceType == SOURCE_TYPE_STOP) { - sectionsPagerAdapter!!.departures = Departure.createDepartures(stopId!!) + Log.i("SQL", "from onCreate") + @Suppress("UNCHECKED_CAST") + sectionsPagerAdapter!!.departures = Departure.createDepartures(stopId!!) as HashMap<String, ArrayList<Departure>> } else { - sectionsPagerAdapter!!.departures = favourite!!.allDepartures() + @Suppress("UNCHECKED_CAST") + sectionsPagerAdapter!!.departures = favourite!!.allDepartures() as HashMap<String, ArrayList<Departure>> } runOnUiThread { sectionsPagerAdapter?.notifyDataSetChanged() @@ -151,7 +155,9 @@ } override fun onVm(vmDepartures: ArrayList<Departure>?, requester: String) { if (timetableType == "departure" && requester == REQUESTER_ID && sourceType == SOURCE_TYPE_STOP) { - val fullDepartures = Departure.createDepartures(stopId!!) + Log.i("SQL", "from onVM") + @Suppress("UNCHECKED_CAST") + val fullDepartures = Departure.createDepartures(stopId!!) as HashMap<String, ArrayList<Departure>> if (vmDepartures != null) { fullDepartures[today.getMode()] = vmDepartures } @@ -172,7 +178,7 @@ private fun scheduleRefresh() { timer.cancel() timer = Timer() createTimerTask() - timer.scheduleAtFixedRate(timerTask, 0, 15000) + timer.scheduleAtFixedRate(timerTask, 15000, 15000) } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -187,20 +193,22 @@ if (id == R.id.action_change_type) { if (timetableType == "departure") { timetableType = "full" item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_departure, this.theme)) + @Suppress("UNCHECKED_CAST") if (sourceType == SOURCE_TYPE_STOP) - sectionsPagerAdapter?.departures = timetable.getStopDepartures(stopId!!) + sectionsPagerAdapter?.departures = timetable.getStopDepartures(stopId!!) as HashMap<String, ArrayList<Departure>> else - sectionsPagerAdapter?.departures = favourite!!.fullTimetable() + sectionsPagerAdapter?.departures = favourite!!.fullTimetable() as HashMap<String, ArrayList<Departure>> sectionsPagerAdapter?.relativeTime = false sectionsPagerAdapter?.notifyDataSetChanged() timer.cancel() } else { timetableType = "departure" item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_full, this.theme)) + @Suppress("UNCHECKED_CAST") if (sourceType == SOURCE_TYPE_STOP) - sectionsPagerAdapter?.departures = Departure.createDepartures(stopId!!) + sectionsPagerAdapter?.departures = Departure.createDepartures(stopId!!) as HashMap<String, ArrayList<Departure>> else - sectionsPagerAdapter?.departures = favourite!!.allDepartures() + sectionsPagerAdapter?.departures = favourite!!.allDepartures() as HashMap<String, ArrayList<Departure>> sectionsPagerAdapter?.relativeTime = true sectionsPagerAdapter?.notifyDataSetChanged() scheduleRefresh() diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt index 50d4709b5dbfc188bc9fcddd85badf7252be8eaf..1b5a3d9e7412c48dfb55ccd93e9b0a6273e80033 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt @@ -1,8 +1,8 @@ package ml.adamsprogs.bimba.models -import android.util.Log import java.util.* import kotlin.collections.ArrayList +import kotlin.collections.HashMap data class Departure(val line: String, val mode: String, val time: String, val lowFloor: Boolean, val modification: String?, val direction: String, val vm: Boolean = false, @@ -37,13 +37,13 @@ } return filtered } - fun createDepartures(stopId: String): HashMap<String, ArrayList<Departure>> { + fun createDepartures(stopId: String): Map<String, List<Departure>> { val timetable = Timetable.getTimetable() val departures = timetable.getStopDepartures(stopId) return createDepartures(departures) } - fun createDepartures(departures: HashMap<String, ArrayList<Departure>>): HashMap<String, ArrayList<Departure>> { + fun createDepartures(departures: Map<String, List<Departure>>): Map<String, List<Departure>> { val moreDepartures = HashMap<String, ArrayList<Departure>>() for ((k, v) in departures) { moreDepartures[k] = ArrayList() diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt index 3a96799815f5e01b97c2f4aba3cc008372308c6b..a851642389c241189f85fecf8482a41aac7854bf 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt @@ -114,11 +114,11 @@ tomorrowDepartures.forEach {twoDayDepartures.add(it)} return twoDayDepartures } - fun allDepartures(): HashMap<String, ArrayList<Departure>> { + fun allDepartures(): Map<String, List<Departure>> { return Departure.createDepartures(timetable.getStopDepartures(timetables)) } - fun fullTimetable(): HashMap<String, ArrayList<Departure>>? { + fun fullTimetable(): Map<String, List<Departure>>? { return timetable.getStopDepartures(timetables) } diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt index 8c5b119273aedac36074beeadf1a4d0ec471328f..d5b9a727035db5a1e46534a1690c96f52d389a6d 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt @@ -5,6 +5,7 @@ import android.database.CursorIndexOutOfBoundsException import android.database.sqlite.SQLiteCantOpenDatabaseException import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabaseCorruptException +import android.util.Log import ml.adamsprogs.bimba.CacheManager import java.io.File @@ -60,10 +61,13 @@ throw SQLiteCantOpenDatabaseException("db corrupt") } this.db = db - cacheManager.recreate(getStopDeparturesByPlates(cacheManager.keys().toSet() as HashSet<Plate>)) //todo optimise + Log.i("SQL", "from refresh") + cacheManager.recreate(getStopDeparturesByPlates(cacheManager.keys().toSet())) + + //todo recreate stops } - fun getStops(): ArrayList<StopSuggestion> { + fun getStops(): List<StopSuggestion> { if (_stops != null) return _stops!! @@ -105,35 +109,43 @@ cursor.close() return number } - fun getStopDepartures(stopId: String, lineId: String? = null): HashMap<String, ArrayList<Departure>> { + fun getStopDepartures(stopId: String): Map<String, List<Departure>> { val plates = HashSet<Plate>() val toGet = HashSet<Plate>() - if (lineId == null) { - getLinesForStop(stopId) - .map { Plate(it, stopId, null) } - .forEach { - if (cacheManager.has(it)) - plates.add(cacheManager.get(it)!!) - else { - toGet.add(it) - } + getLinesForStop(stopId) + .map { Plate(it, stopId, null) } + .forEach { + if (cacheManager.has(it)) + plates.add(cacheManager.get(it)!!) + else { + toGet.add(it) } - } else { - val plate = Plate(lineId, stopId, null) - if (cacheManager.has(plate)) - plates.add(cacheManager.get(plate)!!) - else { - toGet.add(plate) - } + } + + Log.i("SQL", "from (stop)") + getStopDeparturesByPlates(toGet).forEach { cacheManager.push(it); plates.add(it) } + + return Plate.join(plates) + } + + fun getStopDepartures(stopId: String, lineId: String): Map<String, List<Departure>> { + val plates = HashSet<Plate>() + val toGet = HashSet<Plate>() + + val plate = Plate(lineId, stopId, null) + if (cacheManager.has(plate)) + plates.add(cacheManager.get(plate)!!) + else { + toGet.add(plate) } - getStopDeparturesByPlates(toGet).forEach {cacheManager.push(it); plates.add(it)} + getStopDeparturesByPlates(toGet).forEach { cacheManager.push(it); plates.add(it) } return Plate.join(plates) } - fun getStopDepartures(plates: HashSet<Plate>): HashMap<String, ArrayList<Departure>> { + fun getStopDepartures(plates: Set<Plate>): Map<String, ArrayList<Departure>> { val result = HashSet<Plate>() val toGet = HashSet<Plate>() @@ -143,19 +155,55 @@ result.add(cacheManager.get(plate)!!) else toGet.add(plate) } + Log.i("SQL", "from (plates)") - getStopDeparturesByPlates(toGet).forEach {cacheManager.push(it); result.add(it)} + getStopDeparturesByPlates(toGet).forEach { cacheManager.push(it); result.add(it) } return Plate.join(result) } - private fun getStopDeparturesByPlates(plates: HashSet<Plate>): HashSet<Plate> { - val result = HashSet<Plate>() - plates.mapTo(result) { Plate(it.line, it.stop, getStopDeparturesByLine(it.line, it.stop)) } //fixme to one query - return result + private fun getStopDeparturesByPlates(plates: Set<Plate>): Set<Plate> { + if (plates.isEmpty()) + return emptySet() + val result = HashMap<String, Plate>() + + val condition = plates.joinToString(" or ") { "(stop_id = '${it.stop}' and line_id = '${it.line}')" } + + val sql = "select " + + "lines.number, mode, substr('0'||hour, -2) || ':' || " + + "substr('0'||minute, -2) as time, lowFloor, modification, headsign, stop_id, line_id " + + "from " + + "departures join timetables on(timetable_id = timetables.id) join lines on(line_id = lines.id) " + + "where " + + condition + + "order by " + + "mode, time;" + Log.i("SQL", sql) + val cursor = db.rawQuery(sql, null) + + + + while (cursor.moveToNext()) { + val lineId = cursor.getString(7) + val stopId = cursor.getString(6) + + if (!result.containsKey("$lineId@$stopId")) { + result["$lineId@$stopId"] = Plate(lineId, stopId, HashMap()) + result["$lineId@$stopId"]?.departures?.put(MODE_WORKDAYS, HashSet()) + result["$lineId@$stopId"]?.departures?.put(MODE_SATURDAYS, HashSet()) + result["$lineId@$stopId"]?.departures?.put(MODE_SUNDAYS, HashSet()) + } + + result["$lineId@$stopId"]?.departures?.get(cursor.getString(1))?.add( + Departure(cursor.getString(0), cursor.getString(1), + cursor.getString(2), cursor.getInt(3) == 1, + cursor.getString(4), cursor.getString(5))) + } + cursor.close() + return result.values.toSet() } - private fun getLinesForStop(stopId: String): HashSet<String> { + private fun getLinesForStop(stopId: String): Set<String> { val cursor = db.rawQuery("select line_id from timetables where stop_id=?;", listOf(stopId).toTypedArray()) val lines = HashSet<String>() while (cursor.moveToNext()) @@ -164,25 +212,7 @@ cursor.close() return lines } - private fun getStopDeparturesByLine(lineId: String, stopId: String): HashMap<String, HashSet<Departure>>? { - val cursor = db.rawQuery("select lines.number, mode, substr('0'||hour, -2) || ':' || " + - "substr('0'||minute, -2) as time, lowFloor, modification, headsign from departures join " + - "timetables on(timetable_id = timetables.id) join lines on(line_id = lines.id) where " + - "stop_id = ? and line_id = ? order by mode, time;", listOf(stopId, lineId).toTypedArray()) - val departures = HashMap<String, HashSet<Departure>>() - departures.put(MODE_WORKDAYS, HashSet()) - departures.put(MODE_SATURDAYS, HashSet()) - departures.put(MODE_SUNDAYS, HashSet()) - while (cursor.moveToNext()) { //fixme first moveToNext takes 2s, subsequent ones are instant - departures[cursor.getString(1)]?.add(Departure(cursor.getString(0), - cursor.getString(1), cursor.getString(2), cursor.getInt(3) == 1, - cursor.getString(4), cursor.getString(5))) - } - cursor.close() - return departures - } - - fun getLines(stopId: String): ArrayList<String> { + fun getLines(stopId: String): List<String> { val cursor = db.rawQuery(" select distinct line_id from timetables join " + "stops on(stop_id = stops.id) where stops.id = ?;", listOf(stopId).toTypedArray())