Author: Adam Pioterek <adam.pioterek@protonmail.ch>
quicker transition to StopActivity (gettting departures in bg)
%!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/Declinator.kt b/app/src/main/java/ml/adamsprogs/bimba/Declinator.kt index ea7988accf3986731c2ae4d50e2b22832b8841e5..27b3d806e0d1aa507e44faf18ebb8083263f600f 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/Declinator.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/Declinator.kt @@ -3,14 +3,14 @@ class Declinator { companion object { fun decline(number: Long): Int { - when { - number == 0L -> return R.string.now - number % 10 == 0L -> return R.string.departure_in__plural_genitive - number == 1L -> return R.string.departure_in__singular_genitive - number in listOf<Long>(12,13,14) -> return R.string.departure_in__plural_genitive - number % 10 in listOf<Long>(2, 3, 4) -> return R.string.departure_in__plural_nominative - number % 10 in listOf<Long>(1,5,6,7,8,9) -> return R.string.departure_in__plural_genitive - else -> return -1 + return when { + number == 0L -> R.string.now + number % 10 == 0L -> R.string.departure_in__plural_genitive + number == 1L -> R.string.departure_in__singular_genitive + number in listOf<Long>(12,13,14) -> R.string.departure_in__plural_genitive + number % 10 in listOf<Long>(2, 3, 4) -> R.string.departure_in__plural_nominative + number % 10 in listOf<Long>(1,5,6,7,8,9) -> R.string.departure_in__plural_genitive + else -> -1 } } } diff --git a/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt b/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt index ac29fc4fceb1e21e7bb2767cf6d0572bf16d8264..28a62879797264439b8d0a2e62cdf70f76d05ea1 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt @@ -7,8 +7,8 @@ import android.util.Log import ml.adamsprogs.bimba.models.Departure class MessageReceiver : BroadcastReceiver() { - val onTimetableDownloadListeners: HashSet<OnTimetableDownloadListener> = HashSet() - val onVmListeners: HashSet<OnVmListener> = HashSet() + private val onTimetableDownloadListeners: HashSet<OnTimetableDownloadListener> = HashSet() + private val onVmListeners: HashSet<OnVmListener> = HashSet() override fun onReceive(context: Context?, intent: Intent?) { Log.i("Recv", "${intent?.action}") diff --git a/app/src/main/java/ml/adamsprogs/bimba/NetworkStateReceiver.kt b/app/src/main/java/ml/adamsprogs/bimba/NetworkStateReceiver.kt index 71ef786ec8e6cfec1495edf62cdbca6f904f51dd..70a3a1652bc188fb666fd3841247a52422388b1c 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/NetworkStateReceiver.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/NetworkStateReceiver.kt @@ -7,7 +7,7 @@ import android.content.Context class NetworkStateReceiver : BroadcastReceiver() { - val onConnectivityChangeListeners = HashSet<OnConnectivityChangeListener>() + private val onConnectivityChangeListeners = HashSet<OnConnectivityChangeListener>() override fun onReceive(context: Context, intent: Intent) { if (intent.extras != null) { diff --git a/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt b/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt index ac1a62fa93168ced7d984c0ba40a2c68bea52fc4..9912fb6a5c6b5f7ebe188773dba983abccc33370 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt @@ -26,8 +26,8 @@ val RESULT_UP_TO_DATE = "up-to-date" val RESULT_DOWNLOADED = "downloaded" val RESULT_VALIDITY_FAILED = "validity failed" } - lateinit var notificationManager: NotificationManager - var size: Int = 0 + private lateinit var notificationManager: NotificationManager + private var size: Int = 0 override fun onHandleIntent(intent: Intent?) { @@ -138,7 +138,7 @@ e.printStackTrace() } finally { ins.close() val digest = md.digest() - for (i in 0..digest.size - 1) { + for (i in 0 until digest.size) { hex += Integer.toString((digest[i] and 0xff.toByte()) + 0x100, 16).padStart(3, '0').substring(1) } Log.i("Downloader", "checksum is $checksum, and hex is $hex") 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 4f2f7d19ea02042e944d687c3025a1f936a988ad..5322e013f4698048f772d16695b019ff7800c9ee 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt @@ -26,12 +26,12 @@ val context: Context = this val receiver = MessageReceiver() lateinit var timetable: Timetable var stops: ArrayList<StopSuggestion>? = null - lateinit var drawerLayout: DrawerLayout - lateinit var drawer: NavigationView + private lateinit var drawerLayout: DrawerLayout + private lateinit var drawer: NavigationView lateinit var favouritesList: RecyclerView lateinit var searchView: FloatingSearchView lateinit var favourites: FavouriteStorage - var timer = Timer() + private var timer = Timer() private lateinit var timerTask: TimerTask override fun onCreate(savedInstanceState: Bundle?) { @@ -243,16 +243,17 @@ } override fun onTimetableDownload(result: String?) { Log.i("Refresh", "downloaded: $result") - val message: String - when (result) { - TimetableDownloader.RESULT_DOWNLOADED -> message = getString(R.string.timetable_downloaded) - TimetableDownloader.RESULT_NO_CONNECTIVITY -> message = getString(R.string.no_connectivity) - TimetableDownloader.RESULT_UP_TO_DATE -> message = getString(R.string.timetable_up_to_date) - TimetableDownloader.RESULT_VALIDITY_FAILED -> message = getString(R.string.validity_failed) - else -> message = getString(R.string.error_try_later) + val message: String = when (result) { + TimetableDownloader.RESULT_DOWNLOADED -> getString(R.string.timetable_downloaded) + TimetableDownloader.RESULT_NO_CONNECTIVITY -> getString(R.string.no_connectivity) + TimetableDownloader.RESULT_UP_TO_DATE -> getString(R.string.timetable_up_to_date) + TimetableDownloader.RESULT_VALIDITY_FAILED -> getString(R.string.validity_failed) + else -> getString(R.string.error_try_later) + } + if (result == TimetableDownloader.RESULT_DOWNLOADED) { + timetable.refresh(context) + stops = timetable.getStops() } - timetable.refresh() - stops = timetable.getStops() Snackbar.make(findViewById(R.id.drawer_layout), message, Snackbar.LENGTH_LONG).show() } diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt index e8d0b94f23b4029ad6379ebd7e0b6f016aec1293..3034ce46980b184e3776a19abea7046668bbf725 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt @@ -17,15 +17,15 @@ companion object { val EXTRA_FAVOURITE = "favourite" } - lateinit var favourites: FavouriteStorage - lateinit var nameEdit: EditText - var favourite: Favourite? = null + private lateinit var favourites: FavouriteStorage + private lateinit var nameEdit: EditText + private var favourite: Favourite? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit_favourite) - favourite = intent.getParcelableExtra<Favourite>(EXTRA_FAVOURITE) + favourite = intent.getParcelableExtra(EXTRA_FAVOURITE) if (favourite == null) finish() favourites = FavouriteStorage.getFavouriteStorage(this) diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt index b35bf71c9670673bb7ec6a368874560476b3fac0..acd4d6fbe1ef29d2ed450a0aedad5a8be982517b 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt @@ -9,15 +9,15 @@ import ml.adamsprogs.bimba.* class NoDbActivity : AppCompatActivity(), NetworkStateReceiver.OnConnectivityChangeListener, MessageReceiver.OnTimetableDownloadListener { - val networkStateReceiver = NetworkStateReceiver() - val timetableDownloadReceiver = MessageReceiver() - var serviceRunning = false - var askedForNetwork = false + private val networkStateReceiver = NetworkStateReceiver() + private val timetableDownloadReceiver = MessageReceiver() + private var serviceRunning = false + private var askedForNetwork = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_nodb) - var filter: IntentFilter = IntentFilter(TimetableDownloader.ACTION_DOWNLOADED) + var filter = IntentFilter(TimetableDownloader.ACTION_DOWNLOADED) filter.addCategory(Intent.CATEGORY_DEFAULT) registerReceiver(timetableDownloadReceiver, filter) timetableDownloadReceiver.addOnTimetableDownloadListener(this) @@ -34,7 +34,7 @@ } override fun onResume() { super.onResume() - var filter: IntentFilter = IntentFilter(TimetableDownloader.ACTION_DOWNLOADED) + var filter = IntentFilter(TimetableDownloader.ACTION_DOWNLOADED) filter.addCategory(Intent.CATEGORY_DEFAULT) registerReceiver(timetableDownloadReceiver, filter) if (!NetworkStateReceiver.isNetworkAvailable(this)) { @@ -47,7 +47,7 @@ } else if (!serviceRunning) downloadTimetable() } - fun downloadTimetable() { + private fun downloadTimetable() { (findViewById(R.id.noDbCaption) as TextView).text = getString(R.string.no_db_downloading) serviceRunning = true intent = Intent(this, TimetableDownloader::class.java) diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt index aaa8ccb668a1bb08f3507cdc3ccb5537a1796c2e..7e7b3aa73343cc6766039f3e6aea66f64985d915 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt @@ -12,8 +12,11 @@ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) try { - Timetable.getTimetable(this) - startActivity(Intent(this, DashActivity::class.java)) + val timetable = Timetable.getTimetable(this) + if (timetable.isEmpty()) + startActivity(Intent(this, NoDbActivity::class.java)) + else + startActivity(Intent(this, DashActivity::class.java)) } catch(e: SQLiteCantOpenDatabaseException) { startActivity(Intent(this, NoDbActivity::class.java)) } 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 3d8a2d320d9d10d1c84afa08d9e54022be81f941..5ffdd690c00edecc66cb3eb9da03cfc6f223c848 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt @@ -15,6 +15,7 @@ import android.support.v4.content.res.ResourcesCompat import ml.adamsprogs.bimba.models.* import ml.adamsprogs.bimba.* +import kotlin.concurrent.thread class StopActivity : AppCompatActivity(), MessageReceiver.OnVmListener { @@ -57,7 +58,11 @@ viewPager = findViewById(R.id.container) as ViewPager tabLayout = findViewById(R.id.tabs) as TabLayout - sectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager, Departure.createDepartures(stopId)) + sectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager, null) + thread { + sectionsPagerAdapter!!.departures = Departure.createDepartures(stopId) + runOnUiThread { sectionsPagerAdapter?.notifyDataSetChanged() } + } viewPager!!.adapter = sectionsPagerAdapter viewPager!!.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout)) @@ -111,7 +116,7 @@ registerReceiver(receiver, filter) receiver.addOnVmListener(context as MessageReceiver.OnVmListener) } - override fun onVm(vmDepartures: ArrayList<Departure>?, requester:String) { + override fun onVm(vmDepartures: ArrayList<Departure>?, requester: String) { if (timetableType == "departure" && requester == REQUESTER_ID) { val fullDepartures = Departure.createDepartures(stopId) if (vmDepartures != null) { @@ -182,11 +187,11 @@ val rootView = inflater!!.inflate(R.layout.fragment_stop, container, false) val layoutManager = LinearLayoutManager(activity) val departuresList: RecyclerView = rootView.findViewById(R.id.departuresList) as RecyclerView - val dividerItemDecoration = DividerItemDecoration(departuresList.context, layoutManager.orientation) - departuresList.addItemDecoration(dividerItemDecoration) - val adapter = DeparturesAdapter(activity, arguments.getStringArrayList("departures").map { Departure.fromString(it) }, + departuresList.addItemDecoration(DividerItemDecoration(departuresList.context, layoutManager.orientation)) + + val departures = arguments.getStringArrayList("departures")?.map{ Departure.fromString(it) } + departuresList.adapter = DeparturesAdapter(activity, departures, arguments["relativeTime"] as Boolean) - departuresList.adapter = adapter departuresList.layoutManager = layoutManager return rootView } @@ -200,9 +205,12 @@ val fragment = PlaceholderFragment() val args = Bundle() args.putInt(ARG_SECTION_NUMBER, sectionNumber) args.putString("stop", stopId) - val d = ArrayList<String>() - departures?.mapTo(d) { it.toString() } - args.putStringArrayList("departures", d) + if (departures != null) { + val d = ArrayList<String>() + departures.mapTo(d) { it.toString() } + args.putStringArrayList("departures", d) + } else + args.putStringArrayList("departures", null) args.putBoolean("relativeTime", relativeTime) fragment.arguments = args return fragment @@ -210,7 +218,7 @@ } } } - inner class SectionsPagerAdapter(fm: FragmentManager, var departures: HashMap<String, ArrayList<Departure>>) : FragmentStatePagerAdapter(fm) { + inner class SectionsPagerAdapter(fm: FragmentManager, var departures: HashMap<String, ArrayList<Departure>>?) : FragmentStatePagerAdapter(fm) { var relativeTime = true @@ -225,7 +233,7 @@ 0 -> mode = Timetable.MODE_WORKDAYS 1 -> mode = Timetable.MODE_SATURDAYS 2 -> mode = Timetable.MODE_SUNDAYS } - return PlaceholderFragment.newInstance(position + 1, stopId, departures[mode], relativeTime) + return PlaceholderFragment.newInstance(position + 1, stopId, departures?.get(mode), relativeTime) } override fun getCount() = 3 diff --git a/app/src/main/java/ml/adamsprogs/bimba/extensions.kt b/app/src/main/java/ml/adamsprogs/bimba/extensions.kt index 59572d65b5f42dcd6f63cfaa7bda08380ff22c3a..ade30923819f5a428acc3a22b80e1e9376e92f55 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/extensions.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/extensions.kt @@ -4,9 +4,9 @@ import ml.adamsprogs.bimba.models.Timetable import java.util.* internal fun Calendar.getMode(): String { - when (this.get(Calendar.DAY_OF_WEEK)) { - Calendar.SUNDAY -> return Timetable.MODE_SUNDAYS - Calendar.SATURDAY -> return Timetable.MODE_SATURDAYS - else -> return Timetable.MODE_WORKDAYS + return when (this.get(Calendar.DAY_OF_WEEK)) { + Calendar.SUNDAY -> Timetable.MODE_SUNDAYS + Calendar.SATURDAY -> Timetable.MODE_SATURDAYS + else -> Timetable.MODE_WORKDAYS } } \ No newline at end of file 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 a5d9aec9bc7885788f50b9708ed9522134122f12..21df84c11607746d60a7705b6e7b5117c74c5bf5 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt @@ -1,9 +1,10 @@ package ml.adamsprogs.bimba.models import java.util.* +import kotlin.collections.ArrayList -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, +data class Departure(val line: String, private val mode: String, val time: String, private val lowFloor: Boolean, + private val modification: String?, val direction: String, val vm: Boolean = false, var tomorrow: Boolean = false, val onStop: Boolean = false) { override fun toString(): String { @@ -15,20 +16,13 @@ return Departure.fromString(this.toString()) } companion object { - private fun filterDepartures(departures: List<Departure>?): ArrayList<Departure> { + private fun filterDepartures(departures: List<Departure>): ArrayList<Departure> { val filtered = ArrayList<Departure>() val lines = HashMap<String, Int>() - val now = Calendar.getInstance() - for (departure in departures!!) { - val time = Calendar.getInstance() - time.set(Calendar.HOUR_OF_DAY, Integer.parseInt(departure.time.split(":")[0])) - time.set(Calendar.MINUTE, Integer.parseInt(departure.time.split(":")[1])) - time.set(Calendar.SECOND, 0) - time.set(Calendar.MILLISECOND, 0) - if (departure.tomorrow) - time.add(Calendar.DAY_OF_MONTH, 1) + val sortedDepartures = departures.sortedBy { it.timeTill() } + for (departure in sortedDepartures) { var lineExistedTimes = lines[departure.line] - if ((now.before(time) || now == time) && lineExistedTimes ?: 0 < 3) { + if (departure.timeTill() >= 0 && lineExistedTimes ?: 0 < 3) { lineExistedTimes = (lineExistedTimes ?: 0) + 1 lines[departure.line] = lineExistedTimes filtered.add(departure) @@ -40,7 +34,12 @@ fun createDepartures(stopId: String): HashMap<String, ArrayList<Departure>> { val timetable = Timetable.getTimetable() val departures = timetable.getStopDepartures(stopId) - val moreDepartures = timetable.getStopDepartures(stopId) + val moreDepartures = HashMap<String, ArrayList<Departure>>() + for ((k,v) in departures) { + moreDepartures[k] = ArrayList() + for (departure in v) + moreDepartures[k]!!.add(departure.copy()) + } val rolledDepartures = HashMap<String, ArrayList<Departure>>() for ((_, tomorrowDepartures) in moreDepartures) { @@ -50,7 +49,7 @@ for ((mode, _) in departures) { rolledDepartures[mode] = (departures[mode] as ArrayList<Departure> + moreDepartures[mode] as ArrayList<Departure>) as ArrayList<Departure> - rolledDepartures[mode] = filterDepartures(rolledDepartures[mode]) + rolledDepartures[mode] = filterDepartures(rolledDepartures[mode]!!) } return rolledDepartures diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt index c175a6e11d1209aa827274f6484acc3e8ce1c561..63af2772ec7bbdbc190eb3e214a83e173d73aab5 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt @@ -12,13 +12,32 @@ import android.view.LayoutInflater import ml.adamsprogs.bimba.Declinator import java.util.* -class DeparturesAdapter(val context: Context, val departures: List<Departure>, val relativeTime: Boolean) : +class DeparturesAdapter(val context: Context, private val departures: List<Departure>?, private val relativeTime: Boolean) : RecyclerView.Adapter<DeparturesAdapter.ViewHolder>() { + + companion object { + const val VIEW_TYPE_LOADING: Int = 0 + const val VIEW_TYPE_CONTENT: Int = 1 + } + override fun getItemCount(): Int { + + if (departures == null) + return 1 return departures.size } + override fun getItemViewType(position: Int): Int { + return if (departures == null) + VIEW_TYPE_LOADING + else + VIEW_TYPE_CONTENT + } + override fun onBindViewHolder(holder: ViewHolder?, position: Int) { + if (departures == null) { + return + } val departure = departures[position] val now = Calendar.getInstance() val departureTime = Calendar.getInstance() @@ -30,12 +49,12 @@ val departureIn = (departureTime.timeInMillis - now.timeInMillis) / (1000 * 60) val timeString: String - if (departureIn > 60 || departureIn < 0 || !relativeTime) - timeString = context.getString(R.string.departure_at, departure.time) + timeString = if (departureIn > 60 || departureIn < 0 || !relativeTime) + context.getString(R.string.departure_at, departure.time) else if (departureIn > 0 && !departure.onStop) - timeString = context.getString(Declinator.decline(departureIn), departureIn.toString()) + context.getString(Declinator.decline(departureIn), departureIn.toString()) else - timeString = context.getString(R.string.now) + context.getString(R.string.now) val line = holder?.lineTextView line?.text = departure.line @@ -55,8 +74,7 @@ val context = parent?.context val inflater = LayoutInflater.from(context) val rowView = inflater.inflate(R.layout.row_departure, parent, false) - val viewHolder = ViewHolder(rowView) - return viewHolder + return ViewHolder(rowView) } inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 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 828f537146df436aa4ec9c59e56ee1da81ad00e5..357e0ef9d4721ae8a4c5ab3016501a59fb0f8c91 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt @@ -12,11 +12,10 @@ class Favourite : Parcelable, MessageReceiver.OnVmListener { override fun onVm(vmDepartures: ArrayList<Departure>?, requester: String) { val requesterName = requester.split(";")[0] - var requesterTimetable: String - try { - requesterTimetable = requester.split(";")[1] + val requesterTimetable: String = try { + requester.split(";")[1] } catch (e: IndexOutOfBoundsException) { - requesterTimetable = "" + "" } Log.i("VM", "got vm for $requesterName and my name is $name") if (vmDepartures != null && requesterName == name) { @@ -93,7 +92,7 @@ tomorrowCal.add(Calendar.DAY_OF_MONTH, 1) val tomorrow = tomorrowCal.getMode() if (oneDayDepartures == null) { - oneDayDepartures = ArrayList<HashMap<String, ArrayList<Departure>>>() + oneDayDepartures = ArrayList() timetables.mapTo(oneDayDepartures!!) { timetable.getStopDepartures(it[TAG_STOP] as String, it[TAG_LINE]) } } @@ -119,7 +118,7 @@ .minBy { it.timeTill() } } private set - fun filterVmDepartures() { + private fun filterVmDepartures() { this.vmDepartures .filter { it.timeTill() < 0 } .forEach { this.vmDepartures.remove(it) } 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 5672e0a5d124d93edd93d69fe76ac873a0c78c2f..96881bab8514c1b54cb2635ea4e9c7826e832158 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt @@ -8,7 +8,7 @@ import android.widget.ImageView import android.widget.TextView import ml.adamsprogs.bimba.R -class FavouriteEditRowAdapter(var favourite: Favourite) : +class FavouriteEditRowAdapter(private var favourite: Favourite) : RecyclerView.Adapter<FavouriteEditRowAdapter.ViewHolder>() { override fun getItemCount(): Int { return favourite.size @@ -39,8 +39,7 @@ val context = parent?.context val inflater = LayoutInflater.from(context) val rowView = inflater.inflate(R.layout.row_favourite_edit, parent, false) - val viewHolder = ViewHolder(rowView) - return viewHolder + return ViewHolder(rowView) } inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 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 2949ffb25807d7320416e2bf7247b6b6a8d35127..e66d81c93a955e206d16cf36ac0a98f8a49d5794 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt @@ -12,20 +12,20 @@ class FavouriteStorage private constructor(context: Context) : Iterable{ companion object { private var favouriteStorage: FavouriteStorage? = null fun getFavouriteStorage(context: Context? = null): FavouriteStorage { - if (favouriteStorage == null) { + return if (favouriteStorage == null) { if (context == null) throw IllegalArgumentException("requested new storage context not given") else { favouriteStorage = FavouriteStorage(context) - return favouriteStorage as FavouriteStorage + favouriteStorage as FavouriteStorage } } else - return favouriteStorage as FavouriteStorage + favouriteStorage as FavouriteStorage } } val favourites = HashMap<String, Favourite>() - val preferences: SharedPreferences = context.getSharedPreferences("ml.adamsprogs.bimba.prefs", Context.MODE_PRIVATE) + private val preferences: SharedPreferences = context.getSharedPreferences("ml.adamsprogs.bimba.prefs", Context.MODE_PRIVATE) val favouritesList: List<Favourite> get() { return favourites.values.toList() @@ -74,7 +74,7 @@ favourites[name]?.delete(stop, line) serialize() } - fun serialize() { + private fun serialize() { val rootObject = JsonObject() for ((name, favourite) in favourites) { val timetables = JsonArray() @@ -108,7 +108,7 @@ fun merge(names: ArrayList<String>) { if (names.size < 2) return - val newFavourite = Favourite(names[0], ArrayList<HashMap<String, String>>()) + val newFavourite = Favourite(names[0], ArrayList()) 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 be2da2c0ebedf4b2a452871c3a8e73ff3b937627..fdc2ca495df514048bcd8dfc08169088b7bee2bb 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt @@ -18,14 +18,14 @@ import ml.adamsprogs.bimba.Declinator import kotlin.collections.ArrayList //todo list to storage -class FavouritesAdapter(val context: Context, var favourites: List<Favourite>, val onMenuItemClickListener: FavouritesAdapter.OnMenuItemClickListener) : +class FavouritesAdapter(val context: Context, var favourites: List<Favourite>, private val onMenuItemClickListener: FavouritesAdapter.OnMenuItemClickListener) : RecyclerView.Adapter<FavouritesAdapter.ViewHolder>() { - val isSelecting: Boolean + private val isSelecting: Boolean get() { return selected.any { it } } - val selected = ArrayList<Boolean>() + private val selected = ArrayList<Boolean>() val selectedNames: ArrayList<String> get() { val l = ArrayList<String>() @@ -66,7 +66,7 @@ return@thread nextDepartureText = context.getString(Declinator.decline(interval), interval.toString()) nextDepartureLineText = context.getString(R.string.departure_to_line, nextDeparture.line, nextDeparture.direction) } else { - //fixme too early + //fixme too early ? nextDepartureText = context.getString(R.string.no_next_departure) nextDepartureLineText = "" } @@ -102,7 +102,7 @@ } } } - fun toggleSelected(view: CardView, position: Int) { + private fun toggleSelected(view: CardView, position: Int) { growSelected(position) if (selected[position]) @@ -116,7 +116,7 @@ while (position >= selected.size) selected.add(false) } - fun select(view: CardView, position: Int) { + private fun select(view: CardView, position: Int) { growSelected(position) @Suppress("DEPRECATION") @@ -128,7 +128,7 @@ selected[position] = true setSelecting() } - fun unSelect(view: CardView, position: Int) { + private fun unSelect(view: CardView, position: Int) { growSelected(position) val colour = TypedValue() @@ -138,7 +138,7 @@ selected[position] = false setSelecting() } - fun setSelecting() { + private fun setSelecting() { context as Activity if (isSelecting) { context.findViewById(R.id.search_view).visibility = View.INVISIBLE @@ -154,8 +154,7 @@ val context = parent?.context val inflater = LayoutInflater.from(context) val rowView = inflater.inflate(R.layout.row_favourite, parent, false) - val viewHolder = ViewHolder(rowView) - return viewHolder + return ViewHolder(rowView) } fun stopSelecting(name: String) { diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/StopSuggestion.kt b/app/src/main/java/ml/adamsprogs/bimba/models/StopSuggestion.kt index 78a96302b0a4b9131a086c8b47e2a1dffad29263..4cf47653862c3a2204262901f603e67cfcbf2003 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/StopSuggestion.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/StopSuggestion.kt @@ -6,7 +6,7 @@ import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion class StopSuggestion(text: String, val id: String, val symbol: String) : SearchSuggestion { private val body: String = text - val CONTENTS_SUGGESTION = 0x0105 + private val CONTENTS_SUGGESTION = 0x0105 constructor(parcel: Parcel) : this(parcel.readString(), parcel.readString(), parcel.readString()) 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 d77862ff8b8c28ad2712b5a33d985c280aa46963..9f38d413c8d0eada814603ae307b0fb6e4c3e5da 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt @@ -1,10 +1,12 @@ package ml.adamsprogs.bimba.models import android.content.Context +import android.database.CursorIndexOutOfBoundsException import android.database.sqlite.SQLiteCantOpenDatabaseException import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabaseCorruptException import java.io.File + class Timetable private constructor() { companion object { @@ -40,16 +42,26 @@ } lateinit var db: SQLiteDatabase private var _stops: ArrayList<StopSuggestion>? = null + private val _stopDepartures = HashMap<String, HashMap<String, ArrayList<Departure>>>() + private val _stopDeparturesCount = HashMap<String, Int>() - init { - readDbFile() - } + fun refresh(context: Context) { + val db: SQLiteDatabase? + try { + db = SQLiteDatabase.openDatabase(File(context.filesDir, "timetable.db").path, + null, SQLiteDatabase.OPEN_READONLY) + } catch(e: NoSuchFileException) { + throw SQLiteCantOpenDatabaseException("no such file") + } catch(e: SQLiteCantOpenDatabaseException) { + throw SQLiteCantOpenDatabaseException("cannot open db") + } catch(e: SQLiteDatabaseCorruptException) { + throw SQLiteCantOpenDatabaseException("db corrupt") + } + this.db = db - fun refresh() { - readDbFile() - } - - private fun readDbFile() { + for ((k, _) in _stopDepartures) + _stopDepartures.remove(k) + //todo recreate cache } fun getStops(): ArrayList<StopSuggestion> { @@ -95,11 +107,16 @@ return number } fun getStopDepartures(stopId: String, lineId: String? = null, tomorrow: Boolean = false): HashMap<String, ArrayList<Departure>> { - val andLine: String - if (lineId == null) - andLine = "" + val andLine: String = if (lineId == null) + "" else - andLine = "and line_id = '$lineId'" + "and line_id = '$lineId'" + + if (lineId == null && _stopDepartures.contains(stopId)) { + _stopDeparturesCount[stopId] = _stopDeparturesCount[stopId]!! + 1 + return _stopDepartures[stopId]!! + } + _stopDeparturesCount[stopId] = _stopDeparturesCount[stopId]?:0 + 1 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 " + @@ -114,6 +131,19 @@ cursor.getString(1), cursor.getString(2), cursor.getInt(3) == 1, cursor.getString(4), cursor.getString(5), tomorrow = tomorrow)) } 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 } @@ -140,5 +170,17 @@ cursor.moveToNext() element = cursor.getString(0) cursor.close() return element + } + + fun isEmpty(): Boolean { + val cursor = db.rawQuery("select * from metadata;", null) + try { + cursor.moveToNext() + cursor.getString(0) + cursor.close() + } catch(e: CursorIndexOutOfBoundsException) { + return true + } + return false } } diff --git a/app/src/main/res/layout/row_departure.xml b/app/src/main/res/layout/row_departure.xml index 82ad610c4a49c3ee73231d57eddbc75f142d2815..21cef242ab886a2888a793b44d70600eaa40b954 100644 --- a/app/src/main/res/layout/row_departure.xml +++ b/app/src/main/res/layout/row_departure.xml @@ -38,7 +38,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="64dp" android:layout_marginTop="8dp" - android:text="" + android:text="@string/departure_row_getting_departures" android:textAppearance="@style/TextAppearance.AppCompat.Headline" app:layout_constraintStart_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 931fb2eaf4de825a9ff72fb9ac8f103276dfae16..33c0b0bc4be7d5185ece9d532f7809bdca720bea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -65,4 +65,5 @@ "Official timetable is especially prepared for holidays—it will show up as today (if" "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> </resources> diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 6ad589e2c40f195793158af99f92d106103b7221..3d08457d03c0ac3c8ebe6fcebc503caa94e9697f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -55,4 +55,5 @@ "Oficjalny rozkład jest specjalnie przygotowywany na święta — będzie widoczny w dzisiejszej" "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> </resources> \ No newline at end of file diff --git a/build.gradle b/build.gradle index fadd9574797265e217e3c5152b8a46ce3bb9b107..8bb20f456380ed224c6055ed3de7c3076557fe5b 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ maven { url 'https://maven.google.com' } google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-alpha9' + classpath 'com.android.tools.build:gradle:3.0.0-beta2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/research/scraper.py b/research/scraper.py index 1b6777d9f113eed31d9b564aacce15c047923d05..7109b9d730a39c50dfc4babceef440dcbcb85020 100755 --- a/research/scraper.py +++ b/research/scraper.py @@ -8,298 +8,230 @@ bike stations: http://www.ztm.poznan.pl/goeuropa-api/bike-stations """ import json -import hashlib import os import re import sqlite3 import sys -import time import requests from bs4 import BeautifulSoup -import secrets - -def remove_options(text): - return re.sub('(<select[^>]*>([^<]*<option[^>]*>[^<]*</option>)+[^<]*</select>)','', text) - - -def get_validity(): +class TimetableDownloader: """ - get timetable validity + downloader class """ - session = requests.session() - index = session.get('https://www.ztm.poznan.pl/goeuropa-api/index', verify='bundle.pem') - option = re.search('<option value="[0-9]{8}" selected', index.text).group() - return option.split('"')[1] + def __init__(self, verbose): + self.session = requests.session() + self.verbose = verbose -def get_nodes(checksum): - """ - get nodes - """ - session = requests.session() + def __get_validity(self): + """ + get timetable validity + """ + index = self.__get('https://www.ztm.poznan.pl/goeuropa-api/index') + option = re.search('<option value="[0-9]{8}" selected', index.text).group() + return option.split('"')[1] - index = session.get('https://www.ztm.poznan.pl/goeuropa-api/all-nodes', verify='bundle.pem') - new_checksum = hashlib.sha512(index.text.encode('utf-8')).hexdigest() - if checksum == new_checksum: - return None - return [(stop['symbol'], stop['name']) for stop in json.loads(index.text)], new_checksum + def __get_nodes(self): + """ + get nodes + """ + index = self.__get('https://www.ztm.poznan.pl/goeuropa-api/all-nodes') + return [(stop['symbol'], stop['name']) for stop in json.loads(index.text)] -def get_stops(node, checksum): - """ - get stops - """ - session = requests.session() - index = session.get('https://www.ztm.poznan.pl/goeuropa-api/node_stops/{}'.format(node), - verify='bundle.pem') - new_checksum = hashlib.sha512(index.text.encode('utf-8')).hexdigest() - if checksum == new_checksum: - return None - stops = [] - for stop in json.loads(index.text): - stop_id = stop['stop']['id'] - number = re.findall("\\d+", stop['stop']['symbol'])[0] - lat = stop['stop']['lat'] - lon = stop['stop']['lon'] - directions = ', '.join(['{} → {}'.format(transfer['name'], transfer['headsign']) - for transfer in stop['transfers']]) - stops.append((stop_id, node, number, lat, lon, directions)) - return stops, new_checksum + def __get_stops(self, node): + """ + get stops + """ + index = self.__get('https://www.ztm.poznan.pl/goeuropa-api/node_stops/{}'.format(node)) + stops = [] + for stop in json.loads(index.text): + stop_id = stop['stop']['id'] + number = re.findall("\\d+", stop['stop']['symbol'])[0] + lat = stop['stop']['lat'] + lon = stop['stop']['lon'] + directions = ', '.join(['{} → {}'.format(transfer['name'], transfer['headsign']) + for transfer in stop['transfers']]) + stops.append((stop_id, node, number, lat, lon, directions)) + return stops -def get_lines(checksum): - """ - get lines - """ - session = requests.session() + def __get_lines(self): + """ + get lines + """ + index = self.__get('https://www.ztm.poznan.pl/goeuropa-api/index') + soup = BeautifulSoup(index.text, 'html.parser') - index = session.get('https://www.ztm.poznan.pl/goeuropa-api/index', verify='bundle.pem') - index = re.sub('route-modal-[0-9a-f]{7}', '', index.text) - index = remove_options(index) - new_checksum = hashlib.sha512(index.encode('utf-8')).hexdigest() - if new_checksum == checksum: - return None - soup = BeautifulSoup(index, 'html.parser') + lines = {line['data-lineid']: line.text for line in + soup.findAll(attrs={'class': re.compile(r'.*\blineNo-bt\b.*')})} - lines = {line['data-lineid']: line.text for line in - soup.findAll(attrs={'class': re.compile(r'.*\blineNo-bt\b.*')})} + return lines - return lines, new_checksum + def __get_route(self, line_id): + """ + get routes + """ + index = self.__get('https://www.ztm.poznan.pl/goeuropa-api/line-info/{}'.format(line_id)) + soup = BeautifulSoup(index.text, 'html.parser') + directions = soup.findAll(attrs={'class': re.compile(r'.*\baccordion-item\b.*')}) + routes = {} + for direction in directions: + direction_id = direction['data-directionid'] + route = [{'id': stop.find('a')['data-stopid'], 'name': stop['data-name'], + 'onDemand': re.search('stop-onDemand', str(stop['class'])) != None} + for stop in direction.findAll(attrs={'class': re.compile(r'.*\bstop-itm\b.*')})] + routes[direction_id] = route + return routes -def get_route(line_id): - """ - get routes - """ - session = requests.session() - index = session.get('https://www.ztm.poznan.pl/goeuropa-api/line-info/{}'.format(line_id), - verify='bundle.pem') - soup = BeautifulSoup(index.text, 'html.parser') - directions = soup.findAll(attrs={'class': re.compile(r'.*\baccordion-item\b.*')}) - routes = {} - for direction in directions: - direction_id = direction['data-directionid'] - route = [{'id': stop.find('a')['data-stopid'], 'name': stop['data-name'], - 'onDemand': re.search('stop-onDemand', str(stop['class'])) != None} - for stop in direction.findAll(attrs={'class': re.compile(r'.*\bstop-itm\b.*')})] - routes[direction_id] = route - return routes - + def __get_stop_times(self, stop_id, line_id, direction_id): + """ + get timetable + """ + index = self.__post('https://www.ztm.poznan.pl/goeuropa-api/stop-info/{}/{}'. + format(stop_id, line_id), {'directionId': direction_id}) + soup = BeautifulSoup(index.text, 'html.parser') + legends = {} + for row in soup.find(attrs={'class': re.compile(r'.*\blegend-box\b.*')}).findAll('li'): + row = row.text.split('-') + row[0] = row[0].rstrip() + row[1] = row[1].lstrip() + if row[0] != '_': + legends[row[0]] = '-'.join(row[1:]) + schedules = {} + for mode in soup.findAll(attrs={'class': re.compile(r'.*\bmode-tab\b.*')}): + mode_name = mode['data-mode'] + schedule = {row.find('th').text: [ + {'time': minute.text, 'lowFloor': re.search('n-line', str(minute['class'])) != None} + for minute in row.findAll('a')] + for row in mode.find(attrs={'class': re.compile(r'.*\bscheduler-hours\b.*')}). + findAll('tr')} + schedule_2 = {hour: times for hour, times in schedule.items() if times != []} + schedule = [] + for hour, deps in schedule_2.items(): + for dep in deps: + schedule.append((hour, *self.__describe(dep['time'], legends), dep['lowFloor'])) + schedules[mode_name] = schedule -def get_stop_times(stop_id, line_id, direction_id, checksum): - """ - get timetable - """ - session = requests.session() + return schedules - index = session.post('https://www.ztm.poznan.pl/goeuropa-api/stop-info/{}/{}'. - format(stop_id, line_id), data={'directionId': direction_id}, - verify='bundle.pem') - index = re.sub('route-modal-[0-9a-f]{7}', '', index.text) - index = remove_options(index) - new_checksum = hashlib.sha512(index.encode('utf-8')).hexdigest() - if new_checksum == checksum: - return None - soup = BeautifulSoup(index, 'html.parser') - legends = {} - for row in soup.find(attrs={'class': re.compile(r'.*\blegend-box\b.*')}).findAll('li'): - row = row.text.split('-') - row[0] = row[0].rstrip() - row[1] = row[1].lstrip() - if row[0] != '_': - legends[row[0]] = '-'.join(row[1:]) - schedules = {} - for mode in soup.findAll(attrs={'class': re.compile(r'.*\bmode-tab\b.*')}): - mode_name = mode['data-mode'] - schedule = {row.find('th').text: [ - {'time': minute.text, 'lowFloor': re.search('n-line', str(minute['class'])) != None} - for minute in row.findAll('a')] - for row in mode.find(attrs={'class': re.compile(r'.*\bscheduler-hours\b.*')}). - findAll('tr')} - schedule_2 = {hour: times for hour, times in schedule.items() if times != []} - schedule = [] - for hour, deps in schedule_2.items(): - for dep in deps: - schedule.append((hour, *describe(dep['time'], legends), dep['lowFloor'])) - schedules[mode_name] = schedule - return schedules, new_checksum + @staticmethod + def __describe(dep_time, legend): + """ + describe departure + """ + desc = [] + while re.match('^\\d+$', dep_time) is None: + try: + if dep_time[-1] != ',': + desc.append(legend[dep_time[-1]]) + except KeyError: + pass + dep_time = dep_time[:-1] + return (int(dep_time), '; '.join(desc)) -def describe(dep_time, legend): - """ - describe departure - """ - desc = [] - while re.match('^\\d+$', dep_time) is None: + def __get(self, url): try: - if dep_time[-1] != ',': - desc.append(legend[dep_time[-1]]) - except KeyError: - pass - dep_time = dep_time[:-1] - return (int(dep_time), '; '.join(desc)) + return self.session.get(url, verify='bundle.pem') + except: + self.session = requests.session() + return self.session.get(url, verify='bundle.pem') -def main(): - """ - main function - """ - updating = False - changed = False - if os.path.exists('timetable.db'): - updating = True + def __post(self, url, data): + try: + return self.session.post(url, data=data, verify='bundle.pem') + except: + self.session = requests.session() + return self.session.post(url, data=data, verify='bundle.pem') - with sqlite3.connect('timetable.db') as connection: - try: + def download(self): + """ + main function + """ + if os.path.exists('timetable.db'): + connection = sqlite3.connect('timetable.db') cursor = connection.cursor() - if updating: - cursor.execute("select value from metadata where key = 'validFrom'") - current_valid_from = cursor.fetchone()[0] - if get_validity() <= current_valid_from: - return 304 + cursor.execute("select value from metadata where key = 'validFrom'") + current_valid_from = cursor.fetchone()[0] + cursor.close() + connection.close() + if self.__get_validity() <= current_valid_from: + return 304 else: + os.remove('timetable.db') + + with sqlite3.connect('timetable.db') as connection: + try: + cursor = connection.cursor() cursor.execute('create table metadata(key TEXT PRIMARY KEY, value TEXT)') - cursor.execute('create table checksums(checksum TEXT, for TEXT, id TEXT)') cursor.execute('create table nodes(symbol TEXT PRIMARY KEY, name TEXT)') cursor.execute('create table stops(id TEXT PRIMARY KEY, symbol TEXT \ references node(symbol), number TEXT, lat REAL, lon REAL, \ headsigns TEXT)') cursor.execute('create table lines(id TEXT PRIMARY KEY, number TEXT)') - cursor.execute('create table timetables(id TEXT PRIMARY KEY, stop_id TEXT references stop(id), \ - line_id TEXT references line(id), headsign TEXT)') + cursor.execute('create table timetables(id TEXT PRIMARY KEY, stop_id TEXT references \ + stop(id), line_id TEXT references line(id), headsign TEXT)') cursor.execute('create table departures(id INTEGER PRIMARY KEY, \ timetable_id TEXT references timetable(id), \ hour INTEGER, minute INTEGER, mode TEXT, \ lowFloor INTEGER, modification TEXT)') - cursor.execute("delete from metadata where key = 'validFrom'") - validity = get_validity() - print(validity) - cursor.execute("insert into metadata values('validFrom', ?)", (validity,)) - cursor.execute("select checksum from checksums where for = 'nodes'") - checksum = cursor.fetchone() - if checksum != None: - checksum = checksum[0] - else: - checksum = '' - nodes_result = get_nodes(checksum) - if nodes_result is not None: - nodes, checksum = nodes_result - cursor.execute('delete from nodes') - cursor.execute("delete from checksums where for = 'nodes'") - cursor.execute("insert into checksums values(?, 'nodes', null)", (checksum,)) # update + validity = self.__get_validity() + print(validity) + sys.stdout.flush() + cursor.execute("insert into metadata values('validFrom', ?)", (validity,)) + nodes = self.__get_nodes() cursor.executemany('insert into nodes values(?, ?)', nodes) - changed = True - else: - cursor.execute('select * from nodes') - nodes = cursor.fetchall() - nodes = [(sym, nam) for sym, nam, _ in nodes] - nodes_no = len(nodes) - node_i = 1 - for symbol, _ in nodes: - print('node {}'.format(node_i)) - sys.stdout.flush() - cursor.execute("select checksum from checksums where for = 'node' and id = ?", (symbol,)) - checksum = cursor.fetchone() - if checksum != None: - checksum = checksum[0] - else: - checksum = '' - stops_result = get_stops(symbol, checksum) - if stops_result is not None: - stops, checksum = stops_result - cursor.execute('delete from stops where symbol = ?', (symbol,)) + node_i = 1 + for symbol, _ in nodes: + if self.verbose: + print('node {}'.format(node_i)) + stops = self.__get_stops(symbol) cursor.executemany('insert into stops values(?, ?, ?, ?, ?, ?)', stops) - cursor.execute("update checksums set checksum = ? where for = 'node' and id = ?", (checksum, symbol)) - changed = True - node_i += 1 - cursor.execute("select checksum from checksums where for = 'lines'") - checksum = cursor.fetchone() - if checksum != None: - checksum = checksum[0] - else: - checksum = '' - lines_result = get_lines(checksum) - if lines_result is not None: - lines, checksum = lines_result - cursor.execute('delete from lines') - cursor.execute("delete from checksums where for = 'lines'") - cursor.execute("insert into checksums values(?, 'lines', null)", (checksum,)) # update + node_i += 1 + lines = self.__get_lines() cursor.executemany('insert into lines values(?, ?)', lines.items()) - changed = True - else: - cursor.execute('select * from lines') - lines = cursor.fetchall() - lines_no = len(lines) - line_i = 1 - for line_id, _ in lines.items(): - route = get_route(line_id) - routes_no = len(route) - route_i = 1 - for direction, stops in route.items(): - stops_no = len(stops) - stop_i = 1 - for stop in stops: - print("stop {} in route {} in line {}".format(stop_i, route_i, line_i)) - timetable_id = secrets.token_hex(4) - sys.stdout.flush() - cursor.execute("select checksum from checksums where for = 'timetable' and id = ?", (timetable_id,)) - checksum = cursor.fetchone() - if checksum != None: - checksum = checksum[0] - else: - checksum = '' - stop_times = get_stop_times(stop['id'], line_id, direction, checksum) - if stop_times is not None: - timetables, checksum = stop_times - cursor.execute('delete from timetables where line_id = ? and stop_id = ?', - (line_id, stop['id'])) + timetable_id = 1 + line_i = 1 + for line_id, _ in lines.items(): + route = self.__get_route(line_id) + route_i = 1 + for direction, stops in route.items(): + stop_i = 1 + for stop in stops: + if self.verbose: + print("stop {} in route {} in line {}".format(stop_i, route_i, line_i)) + timetables = self.__get_stop_times(stop['id'], line_id, direction) cursor.execute('insert into timetables values(?, ?, ?, ?)', (timetable_id, stop['id'], line_id, stops[-1]['name'])) - cursor.execute("insert into checksums values(?, 'timetable', ?)", (checksum, timetable_id)) - changed = True - cursor.execute('delete from departures where timetable_id = ?', - (timetable_id,)) for mode, times in timetables.items(): cursor.executemany('insert into departures values(null, ?, ?, ?, ?, ?, \ ?)', [(timetable_id, hour, minute, mode, lowfloor, desc) for hour, minute, desc, lowfloor in times]) - stop_i += 1 - sys.stdout.flush() - route_i += 1 - line_i += 1 - except KeyboardInterrupt: - return 404 - if changed: + stop_i += 1 + timetable_id += 1 + route_i += 1 + line_i += 1 + except KeyboardInterrupt: + return 404 return 0 - return 304 if __name__ == '__main__': - exit(main()) + verbose = False + try: + if sys.argv[1] == '-v': + verbose = True + except IndexError: + pass + downloader = TimetableDownloader(verbose) + exit(downloader.download())