Author: Adam Pioterek <adam.pioterek@protonmail.ch>
Better cache
%!v(PANIC=String method: strings: negative Repeat count)
diff --git a/.idea/misc.xml b/.idea/misc.xml index 635999df1e86791ad3787e455b4524e4d8879b93..ba7052b8197ddf8ba8756022d905d03055c7ad60 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -24,7 +24,7 @@ </value> </option> </component> - <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/build/classes" /> </component> <component name="ProjectType"> diff --git a/app/src/main/java/ml/adamsprogs/bimba/CacheManager.kt b/app/src/main/java/ml/adamsprogs/bimba/CacheManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..15832219c47408de967b6edf2b94b02992de65ae --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/CacheManager.kt @@ -0,0 +1,111 @@ +package ml.adamsprogs.bimba + +import android.content.Context +import android.content.SharedPreferences +import ml.adamsprogs.bimba.models.Plate + +class CacheManager private constructor(context: Context) { + companion object { + private var manager: CacheManager? = null + fun getCacheManager(context: Context) : CacheManager { + return if (manager == null) { + manager = CacheManager(context) + manager!! + } else + manager!! + } + } + + private var cachePreferences: SharedPreferences = context.getSharedPreferences("ml.adamsprogs.bimba.cachePreferences.cache", Context.MODE_PRIVATE) + private var cacheHitsPreferences: SharedPreferences = context.getSharedPreferences("ml.adamsprogs.bimba.cachePreferences.cacheHits", Context.MODE_PRIVATE) + + private var cache: HashMap<String, Plate> = HashMap() + private var cacheHits: HashMap<String, Int> = HashMap() + + fun hasAll(plates: HashSet<Plate>): Boolean { + plates + .filterNot { has(it) } + .forEach { return false } + return true + } + + fun hasAny(plates: HashSet<Plate>): Boolean { + plates + .filter { has(it) } + .forEach { return true } + return false + } + + fun has(plate: Plate): Boolean { + val key = "${plate.line}@${plate.stop}" + return cache.containsKey(key) + } + + fun push(plates: HashSet<Plate>) { + val editor = cachePreferences.edit() + for (plate in plates) { + val key = "${plate.line}@${plate.stop}" + cache[key] = plate + editor.putString(key, cache[key].toString()) + } + editor.apply() + } + + fun push(plate: Plate) { + val editorCache = cachePreferences.edit() + val editorCacheHits = cacheHitsPreferences.edit() + if (cacheHits.size == 40) { //todo size? + val key = cacheHits.minBy { it.value }?.key + cache.remove(key) + editorCache.remove(key) + cacheHits.remove(key) + editorCacheHits.remove(key) + } + val key = "${plate.line}@${plate.stop}" + cache[key] = plate + cacheHits[key] = 0 + editorCache.putString(key, plate.toString()) + editorCacheHits.putInt(key, 0) + editorCache.apply() + editorCacheHits.apply() + } + + fun get(plates: HashSet<Plate>): HashSet<Plate> { + val result = HashSet<Plate>() + for (plate in plates) { + val value = get(plate) + if (value == null) + result.add(plate) + else + result.add(get(plate)!!) + } + return result + } + + fun get(plate: Plate): Plate? { + if (!has(plate)) + return null + val key = "${plate.line}@${plate.stop}" + val hits = cacheHits[key] + if (hits != null) + cacheHits[key] = hits + 1 + return cache[key] + } + + fun recreate() { + TODO() + } + + init { + cache = cacheFromString(cachePreferences.all) + cacheHits = cacheHitsPreferences.all as HashMap<String, Int> + } + + private fun cacheFromString(preferences: Map<String, *>): HashMap<String, Plate> { + val result = HashMap<String, Plate>() + for ((key, value) in preferences.entries) { + result[key] = Plate.fromString(value as String) + } + return result + } +} \ No newline at end of file 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 1f2257b90b00f26960d288968e9383a2ae580418..2ed04860296ff1106e3e912c14de22bc2c2c50e3 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt @@ -19,7 +19,6 @@ import android.view.inputmethod.InputMethodManager import ml.adamsprogs.bimba.* import java.util.* import android.os.Bundle -import android.util.Log import kotlinx.android.synthetic.main.activity_dash.* class DashActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener, @@ -74,7 +73,7 @@ super.onOptionsItemSelected(item) } val validity = timetable.getValidity() - drawerView.menu.findItem(R.id.drawer_validity).title = getString(R.string.valid_through, validity) + drawerView.menu.findItem(R.id.drawer_validity).title = getString(R.string.valid_since, validity) searchView = search_view @@ -114,7 +113,6 @@ searchSuggestion as StopSuggestion intent.putExtra(StopActivity.SOURCE_TYPE, StopActivity.SOURCE_TYPE_STOP) intent.putExtra(StopActivity.EXTRA_STOP_ID, searchSuggestion.id) intent.putExtra(StopActivity.EXTRA_STOP_SYMBOL, searchSuggestion.symbol) - Log.i("Profiler", "Intent sent") startActivity(intent) } @@ -158,13 +156,13 @@ override fun run() { for (fav in favourites) { fav.registerOnVm(receiver) for (t in fav.timetables) { - val symbol = timetable.getStopSymbol(t[Favourite.TAG_STOP]!!) - val line = timetable.getLineNumber(t[Favourite.TAG_LINE]!!) + val symbol = timetable.getStopSymbol(t.stop) + val line = timetable.getLineNumber(t.line) val intent = Intent(context, VmClient::class.java) intent.putExtra(VmClient.EXTRA_STOP_SYMBOL, symbol) intent.putExtra(VmClient.EXTRA_LINE_NUMBER, line) intent.putExtra(VmClient.EXTRA_REQUESTER, - "${fav.name};${t[Favourite.TAG_STOP]}${t[Favourite.TAG_LINE]}") + "${fav.name};${t.stop}${t.line}") context.startService(intent) } } 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 7a8fb57b6c09ece0b8185e464fb9abea5a5ddb9e..edd6a40349c9a34d0bb79d63b7d7a9a379e853da 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt @@ -113,11 +113,9 @@ } fab.setOnClickListener { if (!favourites.has(stopSymbol!!)) { - val items = ArrayList<HashMap<String, String>>() + val items = HashSet<Plate>() timetable.getLines(stopId!!).forEach { - val o = HashMap<String, String>() - o[Favourite.TAG_STOP] = stopId!! - o[Favourite.TAG_LINE] = it + val o = Plate(it, stopId!!, null) items.add(o) } favourites.add(stopSymbol as String, items) 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 8be55d625ee4c3f580996c0cf6809352e91a3cb9..4867eed2fb2192b0728f2ae2b3acefd6ebd792df 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt @@ -1,10 +1,9 @@ package ml.adamsprogs.bimba.models -import android.util.Log import java.util.* import kotlin.collections.ArrayList -data class Departure(val line: String, private val mode: String, val time: String, val lowFloor: Boolean, +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, var tomorrow: Boolean = false, val onStop: Boolean = false) { @@ -39,29 +38,23 @@ } fun createDepartures(stopId: String): HashMap<String, ArrayList<Departure>> { val timetable = Timetable.getTimetable() - Log.i("Profiler/Departure", "Got timetable") val departures = timetable.getStopDepartures(stopId) - Log.i("Profiler/Departure", "Got departures") val moreDepartures = HashMap<String, ArrayList<Departure>>() for ((k, v) in departures) { moreDepartures[k] = ArrayList() for (departure in v) moreDepartures[k]!!.add(departure.copy()) } - Log.i("Profiler/Departure", "Duplicated departures") val rolledDepartures = HashMap<String, ArrayList<Departure>>() for ((_, tomorrowDepartures) in moreDepartures) { tomorrowDepartures.forEach { it.tomorrow = true } } - Log.i("Profiler/Departure", "Set tomorrow") for ((mode, _) in departures) { rolledDepartures[mode] = (departures[mode] as ArrayList<Departure> + moreDepartures[mode] as ArrayList<Departure>) as ArrayList<Departure> - Log.i("Profiler/Departure", "Joined departures for $mode") rolledDepartures[mode] = filterDepartures(rolledDepartures[mode]!!) - Log.i("Profiler/Departure", "Filtered departures for $mode") } return rolledDepartures 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 e08b3ad8e021062b0b3f9341225b007b13b904e8..eb89b9b2a1cebfecf7d681916cee29b6dd66e747 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt @@ -7,46 +7,30 @@ import ml.adamsprogs.bimba.getMode import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap +import kotlin.collections.HashSet class Favourite : Parcelable, MessageReceiver.OnVmListener { - override fun onVm(vmDepartures: ArrayList<Departure>?, requester: String) { - val requesterName = requester.split(";")[0] - val requesterTimetable: String = try { - requester.split(";")[1] - } catch (e: IndexOutOfBoundsException) { - "" - } - if (vmDepartures != null && requesterName == name) { - vmDeparturesMap[requesterTimetable] = vmDepartures - this.vmDepartures = vmDeparturesMap.flatMap { it.value } as ArrayList<Departure> - } - filterVmDepartures() - } - private var isRegisteredOnVmListener: Boolean = false var name: String private set - var timetables: ArrayList<HashMap<String, String>> + var timetables: HashSet<Plate> private set - private var oneDayDepartures: ArrayList<HashMap<String, ArrayList<Departure>>>? = null private val vmDeparturesMap = HashMap<String, ArrayList<Departure>>() private var vmDepartures = ArrayList<Departure>() + val timetable = Timetable.getTimetable() + val size: Int + get() = timetables.size constructor(parcel: Parcel) { val array = ArrayList<String>() parcel.readStringList(array) - val timetables = ArrayList<HashMap<String, String>>() - for (row in array) { - val element = HashMap<String, String>() - element[TAG_STOP] = row.split("|")[0] - element[TAG_LINE] = row.split("|")[1] - timetables.add(element) - } + val timetables = HashSet<Plate>() + array.mapTo(timetables) { Plate.fromString(it) } this.name = parcel.readString() this.timetables = timetables } - constructor(name: String, timetables: ArrayList<HashMap<String, String>>) { + constructor(name: String, timetables: HashSet<Plate>) { this.name = name this.timetables = timetables @@ -57,66 +41,19 @@ return Parcelable.CONTENTS_FILE_DESCRIPTOR } override fun writeToParcel(dest: Parcel?, flags: Int) { - val parcel = timetables.map { "${it[TAG_STOP]}|${it[TAG_LINE]}" } + val parcel = timetables.map { it.toString() } dest?.writeStringList(parcel) dest?.writeString(name) } - val timetable = Timetable.getTimetable() - val size: Int - get() = timetables.size - - var nextDeparture: Departure? = null - get() { - filterVmDepartures() - if (timetables.isEmpty() && vmDepartures.isEmpty()) - return null - - if (vmDepartures.isNotEmpty()) { - return vmDepartures.minBy { it.timeTill() } - } - - val twoDayDepartures = ArrayList<Departure>() - val today = Calendar.getInstance().getMode() - val tomorrowCal = Calendar.getInstance() - tomorrowCal.add(Calendar.DAY_OF_MONTH, 1) - val tomorrow = tomorrowCal.getMode() - - if (oneDayDepartures == null) { - oneDayDepartures = ArrayList() - timetables.mapTo(oneDayDepartures!!) { timetable.getStopDepartures(it[TAG_STOP] as String, it[TAG_LINE]) } - } - - oneDayDepartures!!.forEach { - it[today]!!.forEach { - twoDayDepartures.add(it.copy()) - } - } - oneDayDepartures!!.forEach { - it[tomorrow]!!.forEach { - val d = it.copy() - d.tomorrow = true - twoDayDepartures.add(d) - } - } - - if (twoDayDepartures.isEmpty()) - return null - - return twoDayDepartures - .filter { it.timeTill() >= 0 } - .minBy { it.timeTill() } - } - private set - private fun filterVmDepartures() { this.vmDepartures .filter { it.timeTill() < 0 } .forEach { this.vmDepartures.remove(it) } } - fun delete(stop: String, line: String) { - timetables.remove(timetables.find { it[TAG_STOP] == stop && it[TAG_LINE] == line }) + fun delete(plate: Plate) { + timetables.remove(timetables.find { it.stop == plate.stop && it.line == plate.line }) } fun registerOnVm(receiver: MessageReceiver) { @@ -138,9 +75,38 @@ override fun newArray(size: Int): Array<Favourite?> { return arrayOfNulls(size) } + } - val TAG_STOP = "stop" - val TAG_LINE = "line" + fun nextDeparture(): Departure? { + filterVmDepartures() + if (timetables.isEmpty() && vmDepartures.isEmpty()) + return null + + if (vmDepartures.isNotEmpty()) { + return vmDepartures.minBy { it.timeTill() } + } + + val today = Calendar.getInstance().getMode() + val tomorrowCal = Calendar.getInstance() + tomorrowCal.add(Calendar.DAY_OF_MONTH, 1) + val tomorrow = tomorrowCal.getMode() + + val departures = timetable.getStopDepartures(timetables) + val todayDepartures = departures[today]!! + val tomorrowDepartures = ArrayList<Departure>() + val twoDayDepartures = ArrayList<Departure>() + departures[tomorrow]!!.mapTo(tomorrowDepartures) {it.copy()} + tomorrowDepartures.forEach {it.tomorrow = true} + + todayDepartures.forEach {twoDayDepartures.add(it)} + tomorrowDepartures.forEach {twoDayDepartures.add(it)} + + if (twoDayDepartures.isEmpty()) + return null + + return twoDayDepartures + .filter { it.timeTill() >= 0 } + .minBy { it.timeTill() } } fun allDepartures(): HashMap<String, ArrayList<Departure>>? { @@ -149,5 +115,19 @@ } fun fullTimetable(): HashMap<String, ArrayList<Departure>>? { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun onVm(vmDepartures: ArrayList<Departure>?, requester: String) { + val requesterName = requester.split(";")[0] + val requesterTimetable: String = try { + requester.split(";")[1] + } catch (e: IndexOutOfBoundsException) { + "" + } + if (vmDepartures != null && requesterName == name) { + vmDeparturesMap[requesterTimetable] = vmDepartures + this.vmDepartures = vmDeparturesMap.flatMap { it.value } as ArrayList<Departure> + } + filterVmDepartures() } } diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt index 0b729b648a91f5108e529d17b6f5c87b29a515cb..a2cc93ae6b51b9f6ac2e3e6cce371c2621f9fd3a 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt @@ -17,18 +17,17 @@ override fun onBindViewHolder(holder: ViewHolder?, position: Int) { val timetable = Timetable.getTimetable() val favourites = FavouriteStorage.getFavouriteStorage() - val favouriteElement = timetable.getFavouriteElement(favourite.timetables[position][Favourite.TAG_STOP]!!, - favourite.timetables[position][Favourite.TAG_LINE]!!) + val plate = Plate(favourite.timetables.sortedBy { "${it.line}${it.stop}" }[position].line, + favourite.timetables.sortedBy { "${it.line}${it.stop}" }[position].stop, null) + val favouriteElement = timetable.getFavouriteElement(plate) holder?.rowTextView?.text = favouriteElement holder?.splitButton?.setOnClickListener { - favourites.detach(favourite.name, favourite.timetables[position][Favourite.TAG_STOP]!!, - favourite.timetables[position][Favourite.TAG_LINE]!!, favouriteElement) + favourites.detach(favourite.name, plate, favouriteElement) favourite = favourites.favourites[favourite.name]!! notifyDataSetChanged() } holder?.deleteButton?.setOnClickListener { - favourites.delete(favourite.name, favourite.timetables[position][Favourite.TAG_STOP]!!, - favourite.timetables[position][Favourite.TAG_LINE]!!) + favourites.delete(favourite.name, plate) favourite = favourites.favourites[favourite.name]!! notifyDataSetChanged() } diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt index 8ba9f57fafb2e926b510afa6bf3a56824c25f0d3..adee9273c35f60c60c229eb2cb6476988492d678 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt @@ -2,6 +2,7 @@ package ml.adamsprogs.bimba.models import android.content.Context import android.content.SharedPreferences +import android.util.Log import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonObject @@ -34,13 +35,8 @@ init { val favouritesString = preferences.getString("favourites", "{}") val favouritesMap = Gson().fromJson(favouritesString, JsonObject::class.java) for ((name, jsonTimetables) in favouritesMap.entrySet()) { - val timetables = ArrayList<HashMap<String, String>>() - for (jsonTimetable in jsonTimetables.asJsonArray) { - val timetable = HashMap<String, String>() - timetable[Favourite.TAG_STOP] = jsonTimetable.asJsonObject[Favourite.TAG_STOP].asString - timetable[Favourite.TAG_LINE] = jsonTimetable.asJsonObject[Favourite.TAG_LINE].asString - timetables.add(timetable) - } + val timetables = HashSet<Plate>() + jsonTimetables.asJsonArray.mapTo(timetables) { Plate(it.asJsonObject["line"].asString, it.asJsonObject["stop"].asString, null) } favourites[name] = Favourite(name, timetables) } } @@ -49,7 +45,7 @@ override fun iterator(): Iterator= favourites.values.iterator() fun has(name: String): Boolean = favourites.contains(name) - fun add(name: String, timetables: ArrayList<HashMap<String, String>>) { + fun add(name: String, timetables: HashSet<Plate>) { if (favourites[name] == null) { favourites[name] = Favourite(name, timetables) serialize() @@ -68,8 +64,8 @@ favourites.remove(name) serialize() } - fun delete(name: String, stop: String, line: String) { - favourites[name]?.delete(stop, line) + fun delete(name: String, plate: Plate) { + favourites[name]?.delete(plate) serialize() } @@ -79,8 +75,8 @@ for ((name, favourite) in favourites) { val timetables = JsonArray() for (timetable in favourite.timetables) { val element = JsonObject() - element.addProperty(Favourite.TAG_STOP, timetable[Favourite.TAG_STOP]) - element.addProperty(Favourite.TAG_LINE, timetable[Favourite.TAG_LINE]) + element.addProperty("stop", timetable.stop) + element.addProperty("line", timetable.line) timetables.add(element) } rootObject.add(name, timetables) @@ -89,24 +85,22 @@ val favouritesString = Gson().toJson(rootObject) val editor = preferences.edit() editor.putString("favourites", favouritesString) editor.apply() + } - fun detach(name: String, stop: String, line: String, newName: String) { - val element = HashMap<String, String>() - element[Favourite.TAG_STOP] = stop - element[Favourite.TAG_LINE] = line - val array = ArrayList<HashMap<String, String>>() - array.add(element) + fun detach(name: String, plate: Plate, newName: String) { + val array = HashSet<Plate>() + array.add(plate) favourites[newName] = Favourite(newName, array) serialize() - delete(name, stop, line) + delete(name, plate) } fun merge(names: ArrayList<String>) { if (names.size < 2) return - val newFavourite = Favourite(names[0], ArrayList()) + val newFavourite = Favourite(names[0], HashSet()) for (name in names) { newFavourite.timetables.addAll(favourites[name]!!.timetables) favourites.remove(name) diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt index f430213010b49f50058dd0f571ee258ce991bf63..ebbebcbddff627f6d8cf6f741e6adef300005e8b 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt @@ -53,7 +53,7 @@ thread { val favourite = favourites[position] val nextDeparture: Departure? try { - nextDeparture = favourite.nextDeparture + nextDeparture = favourite.nextDeparture() } catch (e: ConcurrentModificationException) { return@thread } diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Plate.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Plate.kt new file mode 100644 index 0000000000000000000000000000000000000000..c62e6982c1416e0b414e513d1be5d9563e2f0dd6 --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Plate.kt @@ -0,0 +1,45 @@ +package ml.adamsprogs.bimba.models + +import android.util.Log + +data class Plate(val line: String, val stop: String, val departures: HashMap<String, HashSet<Departure>>?) { + override fun toString(): String { + var result = "$line=$stop={" + if (departures != null) { + for ((_, column) in departures) + for (departure in column) { + result += departure.toString() + ";" + } + } + result += "}" + return result + } + + companion object { + fun fromString(string: String): Plate { + val s = string.split("=") + val departures = HashMap<String, HashSet<Departure>>() + for (d in s[2].replace("{", "").replace("}", "").split(";")) { + if (d == "") + continue + val dep = Departure.fromString(d) + if (departures[dep.mode] == null) + departures[dep.mode] = HashSet() + departures[dep.mode]!!.add(dep) + } + return Plate(s[0], s[1], departures) + } + + fun join(set: HashSet<Plate>): HashMap<String, ArrayList<Departure>> { + val departures = HashMap<String, ArrayList<Departure>>() + for (plate in set) { + for ((mode, d) in plate.departures!!) { + if (departures[mode] == null) + departures[mode] = ArrayList() + departures[mode]!!.addAll(d.sortedBy { it.time }) + } + } + return departures + } + } +} \ No newline at end of file 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 e36ad93ecce2365dc879c8ebd9bde571e70ab06b..2ca7bd58bf3a7055d735292963e1015965874a92 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,8 @@ 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 @@ -23,15 +25,16 @@ val db: SQLiteDatabase? try { db = SQLiteDatabase.openDatabase(File(context.filesDir, "timetable.db").path, null, SQLiteDatabase.OPEN_READONLY) - } catch(e: NoSuchFileException) { + } catch (e: NoSuchFileException) { throw SQLiteCantOpenDatabaseException("no such file") - } catch(e: SQLiteCantOpenDatabaseException) { + } catch (e: SQLiteCantOpenDatabaseException) { throw SQLiteCantOpenDatabaseException("cannot open db") - } catch(e: SQLiteDatabaseCorruptException) { + } catch (e: SQLiteDatabaseCorruptException) { throw SQLiteCantOpenDatabaseException("db corrupt") } timetable = Timetable() timetable!!.db = db + timetable!!.cacheManager = CacheManager.getCacheManager(context) return timetable as Timetable } else throw IllegalArgumentException("new timetable requested and no context given") @@ -41,27 +44,24 @@ } } lateinit var db: SQLiteDatabase + private lateinit var cacheManager: CacheManager private var _stops: ArrayList<StopSuggestion>? = null - private val _stopDepartures = HashMap<String, HashMap<String, ArrayList<Departure>>>() - private val _stopDeparturesCount = HashMap<String, Int>() fun refresh(context: Context) { val db: SQLiteDatabase? try { db = SQLiteDatabase.openDatabase(File(context.filesDir, "timetable.db").path, null, SQLiteDatabase.OPEN_READONLY) - } catch(e: NoSuchFileException) { + } catch (e: NoSuchFileException) { throw SQLiteCantOpenDatabaseException("no such file") - } catch(e: SQLiteCantOpenDatabaseException) { + } catch (e: SQLiteCantOpenDatabaseException) { throw SQLiteCantOpenDatabaseException("cannot open db") - } catch(e: SQLiteDatabaseCorruptException) { + } catch (e: SQLiteDatabaseCorruptException) { throw SQLiteCantOpenDatabaseException("db corrupt") } this.db = db - for ((k, _) in _stopDepartures) - _stopDepartures.remove(k) - //todo recreate cache + //todo cacheManager.recreate() } fun getStops(): ArrayList<StopSuggestion> { @@ -106,44 +106,80 @@ cursor.close() return number } - fun getStopDepartures(stopId: String, lineId: String? = null, tomorrow: Boolean = false): HashMap<String, ArrayList<Departure>> { - val andLine: String = if (lineId == null) - "" - else - "and line_id = '$lineId'" + fun getStopDepartures(stopId: String, lineId: String? = null): HashMap<String, ArrayList<Departure>> { + val plates = HashSet<Plate>() - if (lineId == null && _stopDepartures.contains(stopId)) { - _stopDeparturesCount[stopId] = _stopDeparturesCount[stopId]!! + 1 - return _stopDepartures[stopId]!! + if (lineId == null) { + for (line in getLinesForStop(stopId)) { + val plate = Plate(line, stopId, null) + if (cacheManager.has(plate)) + plates.add(cacheManager.get(plate)!!) + else { + val p = Plate(line, stopId, getStopDeparturesByLine(line, stopId)) //fixme to one query + plates.add(p) + cacheManager.push(p) + } + } + } else { + val plate = Plate(lineId, stopId, null) + if (cacheManager.has(plate)) + plates.add(cacheManager.get(plate)!!) + else { + val p = Plate(lineId, stopId, getStopDeparturesByLine(lineId, stopId)) + plates.add(p) + cacheManager.push(p) + } } - _stopDeparturesCount[stopId] = _stopDeparturesCount[stopId]?:0 + 1 + + return Plate.join(plates) + } + + fun getStopDepartures(plates: HashSet<Plate>): HashMap<String, ArrayList<Departure>> { + val result = HashSet<Plate>() + val toGet = HashSet<Plate>() + + for (plate in plates) { + if (cacheManager.has(plate)) + result.add(cacheManager.get(plate)!!) + else + toGet.add(plate) + } + + result.addAll(getStopDeparturesByPlates(toGet)) + + 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 getLinesForStop(stopId: String): HashSet<String> { + val cursor = db.rawQuery("select line_id from timetables where stop_id=?;", listOf(stopId).toTypedArray()) + val lines = HashSet<String>() + while (cursor.moveToNext()) + lines.add(cursor.getString(0)) + 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 = ? $andLine order by mode, time;", listOf(stopId).toTypedArray()) - val departures = HashMap<String, ArrayList<Departure>>() - departures.put(MODE_WORKDAYS, ArrayList()) - departures.put(MODE_SATURDAYS, ArrayList()) - departures.put(MODE_SUNDAYS, ArrayList()) + "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), tomorrow = tomorrow)) + cursor.getString(4), cursor.getString(5))) } cursor.close() - if (lineId == null) { - if (_stopDepartures.size < 10) - _stopDepartures[stopId] = departures - else { - for ((key, value) in _stopDeparturesCount) { - if (value < _stopDeparturesCount[stopId]!!) { - _stopDepartures.remove(key) - _stopDepartures[stopId] = departures - break - } - } - } - } return departures } @@ -159,12 +195,17 @@ cursor.close() return lines } - fun getFavouriteElement(stop: String, line: String): String { + fun getFavouriteElement(plate: Plate): String { + val q = "select name || ' (' || stops.symbol || stops.number || '): \n' " + + "|| lines.number || ' → ' || headsign from timetables join stops on (stops.id = stop_id) " + + "join lines on(lines.id = line_id) join nodes on(nodes.symbol = stops.symbol) where " + + "stop_id = ${plate.stop} and line_id = ${plate.line}" + val cursor = db.rawQuery("select name || ' (' || stops.symbol || stops.number || '): \n' " + "|| lines.number || ' → ' || headsign from timetables join stops on (stops.id = stop_id) " + "join lines on(lines.id = line_id) join nodes on(nodes.symbol = stops.symbol) where " + "stop_id = ? and line_id = ?", - listOf(stop, line).toTypedArray()) + listOf(plate.stop, plate.line).toTypedArray()) val element: String cursor.moveToNext() element = cursor.getString(0) @@ -178,7 +219,7 @@ try { cursor.moveToNext() cursor.getString(0) cursor.close() - } catch(e: CursorIndexOutOfBoundsException) { + } catch (e: CursorIndexOutOfBoundsException) { return true } return false @@ -192,3 +233,4 @@ cursor.close() return "%s-%s-%s".format(validity.substring(0..3), validity.substring(4..5), validity.substring(6..7)) } } + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8f3e09faf5ea5ec104651267b31f7897dd7c334e..24540c50b4f52056f76e6445b065aee643c9a272 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,7 +66,7 @@ "it’s Tuesday, it will be on ‘workdays’ tab).\n" "Be sure to consult the messages on\nhttps://www.ztm.poznan.pl/en.\n\n" </string> <string name="departure_row_getting_departures">Getting departures…</string> - <string name="valid_through">Valid since: %1$s</string> + <string name="valid_since">Valid since %1$s</string> <string name="departure_floor" translatable="false">departure floor type (lowFloor)</string> <string name="departure_info" translatable="false">departure info icon</string> </resources> diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 60e683c4bf3f682680d82ee5d72896b2daa1dba4..fcd45d495808aa9f80b0104e9bf71dcb705d0ac5 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -32,7 +32,7 @@ Ładowanie… <string name="departure_in__singular_genitive">Za %1$s minutę</string> <string name="departure_in__plural_genitive">Za %1$s minut</string> <string name="departure_in__plural_nominative">Za %1$s minuty</string> - <string name="home">Dom</string> + <string name="home">Strona główna</string> <string name="refresh">Odśwież</string> <string name="help">Pomoc</string> <string name="title_activity_help">Pomoc</string> @@ -56,5 +56,5 @@ "zakładce (jeśli jest wtorek, to w „dni robocze”).\n" "Pamiętaj, aby sprawdzić aktualności na\nhttps://www.ztm.poznan.pl.\n\n" </string> <string name="departure_row_getting_departures">Zbieranie odjazdów…</string> - <string name="valid_through">Ważny od %1$s</string> + <string name="valid_since">Ważny od %1$s</string> </resources> \ No newline at end of file