Bimba.git

commit fad0fae15ab067d84f7f508a12588c8b7eac32a6

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())