ProviderProxy.kt 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. package ml.adamsprogs.bimba
  2. import android.content.Context
  3. import android.content.Intent
  4. import kotlinx.coroutines.*
  5. import kotlinx.coroutines.android.Main
  6. import ml.adamsprogs.bimba.datasources.VmClient
  7. import ml.adamsprogs.bimba.datasources.VmService
  8. import ml.adamsprogs.bimba.models.Departure
  9. import ml.adamsprogs.bimba.models.Plate
  10. import ml.adamsprogs.bimba.models.StopSegment
  11. import ml.adamsprogs.bimba.models.Timetable
  12. import ml.adamsprogs.bimba.models.suggestions.GtfsSuggestion
  13. import ml.adamsprogs.bimba.models.suggestions.StopSuggestion
  14. import java.util.*
  15. import kotlin.collections.HashMap
  16. class ProviderProxy(context: Context? = null) {
  17. private val vmClient = VmClient.getVmClient()
  18. private var timetable: Timetable = Timetable.getTimetable(context)
  19. private var suggestions = emptyList<GtfsSuggestion>()
  20. private val requests = HashMap<String, Request>()
  21. var mode = if (timetable.isEmpty()) MODE_VM else MODE_FULL
  22. companion object {
  23. const val MODE_FULL = "mode_full"
  24. const val MODE_VM = "mode_vm"
  25. }
  26. fun getSuggestions(query: String = "", callback: (List<GtfsSuggestion>) -> Unit) {
  27. GlobalScope.launch {
  28. suggestions = getStopSuggestions(query) //+ getLineSuggestions(query) //todo<p:v+1> + bike stations, train stations, &c
  29. val filtered = filterSuggestions(query)
  30. launch(Dispatchers.Main) {
  31. callback(filtered)
  32. }
  33. }
  34. }
  35. private suspend fun getStopSuggestions(query: String): List<StopSuggestion> {
  36. val vmSuggestions = withContext(Dispatchers.Default) {
  37. vmClient.getStops(query)
  38. }
  39. return if (vmSuggestions.isEmpty() and !timetable.isEmpty()) {
  40. timetable.getStopSuggestions()
  41. } else {
  42. vmSuggestions
  43. }
  44. }
  45. private fun filterSuggestions(query: String): List<GtfsSuggestion> {
  46. return suggestions.filter {
  47. deAccent(it.name).contains(deAccent(query), true)
  48. }
  49. }
  50. private fun deAccent(str: String): String {
  51. var result = str.replace('ę', 'e', true)
  52. result = result.replace('ó', 'o', true)
  53. result = result.replace('ą', 'a', true)
  54. result = result.replace('ś', 's', true)
  55. result = result.replace('ł', 'l', true)
  56. result = result.replace('ż', 'z', true)
  57. result = result.replace('ź', 'z', true)
  58. result = result.replace('ć', 'c', true)
  59. result = result.replace('ń', 'n', true)
  60. return result
  61. }
  62. fun getSheds(name: String, callback: (Map<String, Set<String>>) -> Unit) {
  63. GlobalScope.launch {
  64. val vmSheds = vmClient.getSheds(name)
  65. val sheds = if (vmSheds.isEmpty() and !timetable.isEmpty()) {
  66. timetable.getHeadlinesForStop(name)
  67. } else {
  68. vmSheds
  69. }
  70. launch(Dispatchers.Main) {
  71. callback(sheds)
  72. }
  73. }
  74. }
  75. fun subscribeForDepartures(stopSegments: Set<StopSegment>, listener: OnDeparturesReadyListener, context: Context): String {
  76. stopSegments.forEach {
  77. val intent = Intent(context, VmService::class.java)
  78. intent.putExtra("stop", it.stop)
  79. intent.action = "request"
  80. context.startService(intent)
  81. }
  82. val uuid = UUID.randomUUID().toString()
  83. requests[uuid] = Request(listener, stopSegments)
  84. return uuid
  85. }
  86. fun subscribeForDepartures(stopCode: String, listener: OnDeparturesReadyListener, context: Context): String {
  87. val intent = Intent(context, VmService::class.java)
  88. intent.putExtra("stop", stopCode)
  89. intent.action = "request"
  90. context.startService(intent)
  91. val uuid = UUID.randomUUID().toString()
  92. requests[uuid] = Request(listener, setOf(StopSegment(stopCode, null)))
  93. return uuid
  94. }
  95. private fun constructSegmentDepartures(stopSegments: Set<StopSegment>): Deferred<Map<String, List<Departure>>> {
  96. return GlobalScope.async {
  97. if (timetable.isEmpty())
  98. emptyMap()
  99. else {
  100. timetable.getStopDeparturesBySegments(stopSegments)
  101. }
  102. }
  103. }
  104. private fun filterDepartures(departures: Map<String, List<Departure>>): List<Departure> {
  105. val now = Calendar.getInstance().secondsAfterMidnight()
  106. val lines = HashMap<String, Int>()
  107. val twoDayDepartures = (timetable.getServiceForToday()?.let {
  108. departures[it]
  109. } ?: emptyList()) +
  110. (timetable.getServiceForTomorrow()?.let { service ->
  111. departures[service]!!.map { it.copy().apply { tomorrow = true } }
  112. } ?: emptyList())
  113. return twoDayDepartures
  114. .filter { it.timeTill(now) >= 0 }
  115. .filter {
  116. val existed = lines[it.line] ?: 0
  117. if (existed < 3) {
  118. lines[it.line] = existed + 1
  119. true
  120. } else false
  121. }
  122. }
  123. fun unsubscribeFromDepartures(uuid: String, context: Context) {
  124. requests[uuid]?.unsubscribe(context)
  125. requests.remove(uuid)
  126. }
  127. fun refreshTimetable(context: Context) {
  128. timetable = Timetable.getTimetable(context, true)
  129. mode = MODE_FULL
  130. }
  131. fun getFullTimetable(stopCode: String): Map<String, List<Departure>> {
  132. return if (timetable.isEmpty())
  133. emptyMap()
  134. else
  135. timetable.getStopDepartures(stopCode)
  136. }
  137. fun getFullTimetable(stopSegments: Set<StopSegment>): Map<String, List<Departure>> {
  138. return if (timetable.isEmpty())
  139. emptyMap()
  140. else
  141. timetable.getStopDeparturesBySegments(stopSegments)
  142. }
  143. fun fillStopSegment(stopSegment: StopSegment, callback: (StopSegment?) -> Unit) {
  144. GlobalScope.launch {
  145. callback(fillStopSegment(stopSegment))
  146. }
  147. }
  148. suspend fun fillStopSegment(stopSegment: StopSegment): StopSegment? {
  149. if (stopSegment.plates != null)
  150. return stopSegment
  151. return if (timetable.isEmpty())
  152. vmClient.getDirections(stopSegment.stop)
  153. else
  154. timetable.getHeadlinesForStopCode(stopSegment.stop)
  155. }
  156. fun getStopName(stopCode: String, callback: (String?) -> Unit) {
  157. GlobalScope.launch {
  158. callback(getStopName(stopCode))
  159. }
  160. }
  161. suspend fun getStopName(stopCode: String): String? {
  162. return if (timetable.isEmpty())
  163. vmClient.getName(stopCode)
  164. else
  165. timetable.getStopName(stopCode)
  166. }
  167. fun describeService(service: String, context: Context): String? {
  168. return if (timetable.isEmpty())
  169. null
  170. else
  171. timetable.getServiceDescription(service, context)
  172. }
  173. fun getServiceFirstDay(service: String): Int {
  174. return timetable.getServiceFirstDay(service)
  175. }
  176. interface OnDeparturesReadyListener {
  177. fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?, code: Int)
  178. }
  179. inner class Request(private val listener: OnDeparturesReadyListener, private val segments: Set<StopSegment>) : MessageReceiver.OnVmListener {
  180. private val receiver = MessageReceiver.getMessageReceiver()
  181. private val receivedPlates = HashSet<Plate.ID>()
  182. private var cache: Deferred<Map<String, List<Departure>>>? = null
  183. init {
  184. receiver.addOnVmListener(this@Request)
  185. GlobalScope.launch {
  186. cache = constructSegmentDepartures(segments)
  187. }
  188. }
  189. override fun onVm(vmDepartures: Set<Departure>?, plateId: Plate.ID?, stopCode: String, code: Int) {
  190. GlobalScope.launch(Dispatchers.Main) {
  191. if ((plateId == null || vmDepartures == null) and (timetable.isEmpty())) {
  192. listener.onDeparturesReady(emptyList(), null, code)
  193. return@launch
  194. }
  195. if (plateId == null) {
  196. listener.onDeparturesReady(filterDepartures(cache!!.await()), null, code)
  197. } else {
  198. if (segments.any { plateId in it }) {
  199. if (vmDepartures != null) {
  200. listener.onDeparturesReady(vmDepartures.toList(), plateId, code)
  201. if (plateId !in receivedPlates)
  202. receivedPlates.add(plateId)
  203. } else {
  204. receivedPlates.remove(plateId)
  205. if (receivedPlates.isEmpty()) {
  206. listener.onDeparturesReady(filterDepartures(cache!!.await()), null, code)
  207. }
  208. }
  209. }
  210. }
  211. }
  212. }
  213. fun unsubscribe(context: Context) {
  214. segments.forEach {
  215. val intent = Intent(context, VmService::class.java)
  216. intent.putExtra("stop", it.stop)
  217. intent.action = "remove"
  218. context.startService(intent)
  219. }
  220. receiver.removeOnVmListener(this)
  221. }
  222. }
  223. }