Page MenuHomeGitPull.it

D83.1780197730.diff
No OneTemporary

Authored By
Unknown
Size
151 KB
Referenced Files
None
Subscribers
None

D83.1780197730.diff

diff --git a/GTFS_changes.diff b/GTFS_changes.diff
new file mode 100644
--- /dev/null
+++ b/GTFS_changes.diff
@@ -0,0 +1,501 @@
+diff --git a/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
+index 453630e..5f6e054 100644
+--- a/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
++++ b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
+@@ -19,13 +19,12 @@ package it.reyboz.bustorino.data.gtfs
+
+ import android.content.Context
+ import android.util.Log
+-import java.util.ArrayList
+
+ class CsvTableInserter(
+ val tableName: String, context: Context
+ ) {
+ private val database: GtfsDatabase = GtfsDatabase.getGtfsDatabase(context)
+- private val dao: StaticGtfsDao = database.gtfsDao()
++ private val databaseDao: GtfsDBDao = database.gtfsDao()
+
+ private val elementsList: MutableList< in GtfsTable> = mutableListOf()
+
+@@ -35,12 +34,12 @@ class CsvTableInserter(
+ private var countInsert = 0
+ init {
+ if(tableName == "stop_times") {
+- stopsIDsPresent = dao.getAllStopsIDs().toHashSet()
+- tripsIDsPresent = dao.getAllTripsIDs().toHashSet()
++ stopsIDsPresent = databaseDao.getAllStopsIDs().toHashSet()
++ tripsIDsPresent = databaseDao.getAllTripsIDs().toHashSet()
+ Log.d(DEBUG_TAG, "num stop IDs present: "+ stopsIDsPresent!!.size)
+ Log.d(DEBUG_TAG, "num trips IDs present: "+ tripsIDsPresent!!.size)
+ } else if(tableName == "routes"){
+- dao.deleteAllRoutes()
++ databaseDao.deleteAllRoutes()
+ }
+ }
+
+@@ -77,7 +76,7 @@ class CsvTableInserter(
+ //have to insert
+
+ if (tableName == "routes")
+- dao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
++ databaseDao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
+ else
+ insertDataInDatabase()
+
+@@ -90,21 +89,21 @@ class CsvTableInserter(
+ countInsert += elementsList.size
+ when(tableName){
+ "stops" -> {
+- dao.insertStops(elementsList.filterIsInstance<GtfsStop>())
++ databaseDao.insertStops(elementsList.filterIsInstance<GtfsStop>())
+ }
+- "routes" -> dao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
+- "calendar" -> dao.insertServices(elementsList.filterIsInstance<GtfsService>())
+- "calendar_dates" -> dao.insertDates(elementsList.filterIsInstance<GtfsServiceDate>())
+- "trips" -> dao.insertTrips(elementsList.filterIsInstance<GtfsTrip>())
+- "stop_times"-> dao.insertStopTimes(elementsList.filterIsInstance<GtfsStopTime>())
+- "shapes" -> dao.insertShapes(elementsList.filterIsInstance<GtfsShape>())
++ "routes" -> databaseDao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
++ "calendar" -> databaseDao.insertServices(elementsList.filterIsInstance<GtfsService>())
++ "calendar_dates" -> databaseDao.insertDates(elementsList.filterIsInstance<GtfsServiceDate>())
++ "trips" -> databaseDao.insertTrips(elementsList.filterIsInstance<GtfsTrip>())
++ "stop_times"-> databaseDao.insertStopTimes(elementsList.filterIsInstance<GtfsStopTime>())
++ "shapes" -> databaseDao.insertShapes(elementsList.filterIsInstance<GtfsShape>())
+
+ }
+ ///if(elementsList.size < MAX_ELEMENTS)
+ }
+ fun finishInsert(){
+ insertDataInDatabase()
+- Log.d(DEBUG_TAG, "Inserted "+countInsert+" elements from "+tableName);
++ Log.d(DEBUG_TAG, "Inserted $countInsert elements from $tableName")
+ }
+
+ companion object{
+diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt
+new file mode 100644
+index 0000000..e53729a
+--- /dev/null
++++ b/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt
+@@ -0,0 +1,55 @@
++package it.reyboz.bustorino.data.gtfs
++
++import androidx.room.ColumnInfo
++import androidx.room.Embedded
++import androidx.room.Entity
++import androidx.room.PrimaryKey
++
++@Entity(tableName = GtfsAgency.TABLE_NAME)
++data class GtfsAgency(
++ @PrimaryKey
++ @ColumnInfo(name = COL_GTFS_ID)
++ val gtfsId: String,
++ @ColumnInfo(name = COL_NAME)
++ val name: String,
++ @ColumnInfo(name = COL_URL)
++ val url: String,
++ @ColumnInfo(name = COL_FAREURL)
++ val fareUrl: String?,
++ @ColumnInfo(name = COL_PHONE)
++ val phone: String?,
++ @Embedded var feed: GtfsFeed?
++): GtfsTable{
++ constructor(valuesByColumn: Map<String,String>) : this(
++ valuesByColumn[COL_GTFS_ID]!!,
++ valuesByColumn[COL_NAME]!!,
++ valuesByColumn[COL_URL]!!,
++ valuesByColumn[COL_FAREURL],
++ valuesByColumn[COL_PHONE],
++ null
++ )
++
++ companion object{
++ const val TABLE_NAME="gtfs_agencies"
++
++ const val COL_GTFS_ID="gtfs_id"
++ const val COL_NAME="ag_name"
++ const val COL_URL="ag_url"
++ const val COL_FAREURL = "fare_url"
++ const val COL_PHONE = "phone"
++
++ val COLUMNS = arrayOf(
++ COL_GTFS_ID,
++ COL_NAME,
++ COL_URL,
++ COL_FAREURL,
++ COL_PHONE
++ )
++ const val CREATE_SQL =
++ "CREATE TABLE $TABLE_NAME ( $COL_GTFS_ID )"
++ }
++
++ override fun getColumns(): Array<String> {
++ return COLUMNS
++ }
++}
+diff --git a/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
+similarity index 80%
+rename from src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
+rename to src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
+index 32f862c..5a00e74 100644
+--- a/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
++++ b/src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
+@@ -21,8 +21,8 @@ import androidx.lifecycle.LiveData
+ import androidx.room.*
+
+ @Dao
+-interface StaticGtfsDao {
+- @Query("SELECT * FROM "+GtfsRoute.DB_TABLE+" ORDER BY "+GtfsRoute.COL_SORT_ORDER)
++interface GtfsDBDao {
++ @Query("SELECT * FROM "+GtfsRoute.DB_TABLE)
+ fun getAllRoutes() : LiveData<List<GtfsRoute>>
+
+ @Query("SELECT "+GtfsTrip.COL_TRIP_ID+" FROM "+GtfsTrip.DB_TABLE)
+@@ -45,9 +45,9 @@ interface StaticGtfsDao {
+ deleteAllRoutes()
+ insertRoutes(routes)
+ }
+-
++ @Transaction
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+- fun insertRoutes(users: List<GtfsRoute>)
++ fun insertRoutes(routes: List<GtfsRoute>)
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertStops(stops: List<GtfsStop>)
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+@@ -87,4 +87,21 @@ interface StaticGtfsDao {
+ @Query("DELETE FROM "+GtfsService.DB_TABLE)
+ fun deleteAllServices()
+
++ @Insert(onConflict = OnConflictStrategy.REPLACE)
++ fun insertFeeds(feeds: List<GtfsFeed>)
++
++ @Insert(onConflict = OnConflictStrategy.REPLACE)
++ fun insertAgencies(agencies: List<GtfsAgency>)
++
++ @Transaction
++ fun insertAgenciesWithFeeds(feeds: List<GtfsFeed>, agencies: List<GtfsAgency>){
++ insertFeeds(feeds)
++ insertAgencies(agencies)
++ }
++
++ @Insert(onConflict = OnConflictStrategy.REPLACE)
++ fun insertPatterns(patterns: List<MatoPattern>)
++
++ @Insert(onConflict = OnConflictStrategy.REPLACE)
++ fun insertPatternStops(patternStops: List<PatternStop>)
+ }
+\ No newline at end of file
+diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
+index 986f01f..c6f19a7 100644
+--- a/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
++++ b/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
+@@ -22,20 +22,24 @@ import androidx.room.*
+
+ @Database(
+ entities = [
++ GtfsFeed::class,
++ GtfsAgency::class,
+ GtfsServiceDate::class,
+ GtfsStop::class,
+ GtfsService::class,
+ GtfsRoute::class,
+ GtfsStopTime::class,
+ GtfsTrip::class,
+- GtfsShape::class],
++ GtfsShape::class,
++ MatoPattern::class,
++ PatternStop::class
++ ],
+ version = GtfsDatabase.VERSION,
+- exportSchema = false,
+ )
+ @TypeConverters(Converters::class)
+-public abstract class GtfsDatabase : RoomDatabase() {
++abstract class GtfsDatabase : RoomDatabase() {
+
+- abstract fun gtfsDao() : StaticGtfsDao
++ abstract fun gtfsDao() : GtfsDBDao
+
+ companion object{
+ @Volatile
+@@ -51,7 +55,13 @@ public abstract class GtfsDatabase : RoomDatabase() {
+ }
+ }
+
+- const val VERSION = 1
++ const val VERSION = 2
+ const val FOREIGNKEY_ONDELETE = ForeignKey.CASCADE
++
++ /*val MIGRATION_1_2 = Migration(1,2) {
++ TODO("Have to write it") //it.execSQL()
++ }
++
++ */
+ }
+ }
+\ No newline at end of file
+diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt
+new file mode 100644
+index 0000000..8105fa6
+--- /dev/null
++++ b/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt
+@@ -0,0 +1,50 @@
++/*
++ BusTO - Data components
++ Copyright (C) 2022 Fabio Mazza
++
++ This program is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++package it.reyboz.bustorino.data.gtfs
++
++import androidx.room.ColumnInfo
++import androidx.room.Entity
++import androidx.room.PrimaryKey
++
++@Entity(tableName = GtfsFeed.TABLE_NAME)
++data class GtfsFeed(
++ @PrimaryKey
++ @ColumnInfo(name = COL_GTFS_ID)
++ val gtfsId: String,
++): GtfsTable{
++ constructor(valuesByColumn: Map<String,String>) : this(
++ valuesByColumn[COL_GTFS_ID]!!,
++ )
++
++ companion object{
++ const val TABLE_NAME="gtfs_feeds"
++
++ const val COL_GTFS_ID="feed_id"
++
++
++ val COLUMNS = arrayOf(
++ COL_GTFS_ID,
++ )
++ const val CREATE_SQL =
++ "CREATE TABLE $TABLE_NAME ( $COL_GTFS_ID )"
++ }
++
++ override fun getColumns(): Array<String> {
++ return COLUMNS
++ }
++}
+diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt
+new file mode 100644
+index 0000000..a9b7f2a
+--- /dev/null
++++ b/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt
+@@ -0,0 +1,19 @@
++package it.reyboz.bustorino.data.gtfs
++
++enum class GtfsMode(val intType: Int) {
++ TRAM(0),
++ SUBWAY(1),
++ RAIL(2),
++ BUS(3),
++ FERRY(4),
++ CABLE_TRAM(5),
++ GONDOLA(6),
++ FUNICULAR(7),
++ TROLLEYBUS(11),
++ MONORAIL(12);
++
++ companion object {
++ private val VALUES = values()
++ fun getByValue(value: Int) = VALUES.firstOrNull { it.intType == value }
++ }
++}
+\ No newline at end of file
+diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
+index fef6167..fa39e16 100644
+--- a/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
++++ b/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
+@@ -23,26 +23,25 @@ import androidx.room.PrimaryKey
+
+ @Entity(tableName=GtfsRoute.DB_TABLE)
+ data class GtfsRoute(
+- @PrimaryKey @ColumnInfo(name = COL_ROUTE_ID)
+- val ID: String,
+- @ColumnInfo(name = "agency_id")
++ @PrimaryKey @ColumnInfo(name = COL_ROUTE_ID)
++ val gtfsId: String,
++ @ColumnInfo(name = "agency_id")
+ val agencyID: String,
+- @ColumnInfo(name = "route_short_name")
++ @ColumnInfo(name = "route_short_name")
+ val shortName: String,
+- @ColumnInfo(name = "route_long_name")
++ @ColumnInfo(name = "route_long_name")
+ val longName: String,
+- @ColumnInfo(name = "route_desc")
++ @ColumnInfo(name = "route_desc")
+ val description: String,
+- @ColumnInfo(name ="route_type")
+- val type: String,
++ @ColumnInfo(name = COL_MODE)
++ val mode: GtfsMode,
+ //@ColumnInfo(name ="route_url")
+ //val url: String,
+- @ColumnInfo(name ="route_color")
++ @ColumnInfo(name = COL_COLOR)
+ val color: String,
+- @ColumnInfo(name ="route_text_color")
++ @ColumnInfo(name = COL_TEXT_COLOR)
+ val textColor: String,
+- @ColumnInfo(name = COL_SORT_ORDER)
+- val sortOrder: Int
++
+ ): GtfsTable {
+
+ constructor(valuesByColumn: Map<String,String>) : this(
+@@ -51,15 +50,17 @@ data class GtfsRoute(
+ valuesByColumn["route_short_name"]!!,
+ valuesByColumn["route_long_name"]!!,
+ valuesByColumn["route_desc"]!!,
+- valuesByColumn["route_type"]!!,
+- valuesByColumn["route_color"]!!,
+- valuesByColumn["route_text_color"]!!,
+- valuesByColumn[COL_SORT_ORDER]?.toInt()!!
++ valuesByColumn["route_type"]?.toInt()?.let { GtfsMode.getByValue(it) }!!,
++ valuesByColumn[COL_COLOR]!!,
++ valuesByColumn[COL_TEXT_COLOR]!!,
+ )
+ companion object {
+ const val DB_TABLE: String="routes_table"
+ const val COL_SORT_ORDER: String="route_sort_order"
+ const val COL_ROUTE_ID = "route_id"
++ const val COL_MODE ="route_mode"
++ const val COL_COLOR="route_color"
++ const val COL_TEXT_COLOR="route_text_color"
+
+ val COLUMNS = arrayOf(COL_ROUTE_ID,
+ "agency_id",
+diff --git a/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt b/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt
+new file mode 100644
+index 0000000..83a0bb5
+--- /dev/null
++++ b/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt
+@@ -0,0 +1,111 @@
++/*
++ BusTO - Data components
++ Copyright (C) 2022 Fabio Mazza
++
++ This program is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++package it.reyboz.bustorino.data.gtfs
++
++import androidx.room.ColumnInfo
++import androidx.room.Entity
++import androidx.room.Ignore
++import androidx.room.PrimaryKey
++import it.reyboz.bustorino.backend.Stop
++
++@Entity(tableName = MatoPattern.TABLE_NAME)
++data class MatoPattern(
++ @ColumnInfo(name= COL_NAME)
++ val name: String,
++ @ColumnInfo(name= COL_CODE)
++ @PrimaryKey
++ val code: String,
++ @ColumnInfo(name= COL_SEMANTIC_HASH)
++ val semanticHash: String,
++ @ColumnInfo(name= COL_DIRECTION_ID)
++ val directionId: Int,
++ @ColumnInfo(name= COL_ROUTE_ID)
++ val routeGtfsId: String,
++ @ColumnInfo(name= COL_HEADSIGN)
++ var headsign: String?,
++ @ColumnInfo(name= COL_GEOMETRY_POLY)
++ val patternGeometryPoly: String,
++ @ColumnInfo(name= COL_GEOMETRY_LENGTH)
++ val patternGeometryLength: Int,
++ @Ignore
++ val stopsGtfsIDs: ArrayList<String>
++
++):GtfsTable{
++
++ @Ignore
++ val servingStops= ArrayList<Stop>(4)
++ constructor(
++ name: String, code:String,
++ semanticHash: String, directionId: Int,
++ routeGtfsId: String, headsign: String?,
++ patternGeometryPoly: String, patternGeometryLength: Int
++ ): this(name, code, semanticHash, directionId, routeGtfsId, headsign, patternGeometryPoly, patternGeometryLength, ArrayList<String>(4))
++
++ companion object{
++ const val TABLE_NAME="mato_patterns"
++
++ const val COL_NAME="pattern_name"
++ const val COL_CODE="pattern_code"
++ const val COL_ROUTE_ID="pattern_route_id"
++ const val COL_SEMANTIC_HASH="pattern_hash"
++ const val COL_DIRECTION_ID="pattern_direction_id"
++ const val COL_HEADSIGN="pattern_headsign"
++ const val COL_GEOMETRY_POLY="pattern_polyline"
++ const val COL_GEOMETRY_LENGTH="pattern_polylength"
++
++ val COLUMNS = arrayOf(
++ COL_NAME,
++ COL_CODE,
++ COL_ROUTE_ID,
++ COL_SEMANTIC_HASH,
++ COL_DIRECTION_ID,
++ COL_HEADSIGN,
++ COL_GEOMETRY_POLY,
++ COL_GEOMETRY_LENGTH
++ )
++ }
++ override fun getColumns(): Array<String> {
++ return COLUMNS
++ }
++}
++
++//DO NOT USE EMBEDDED!!! -> copies all data
++
++@Entity(tableName=PatternStop.TABLE_NAME,
++ primaryKeys = [
++ PatternStop.COL_PATTERN_ID,
++ PatternStop.COL_STOP_GTFS,
++ PatternStop.COL_ORDER
++ ]
++)
++data class PatternStop(
++ @ColumnInfo(name= COL_PATTERN_ID)
++ val patternId: String,
++ @ColumnInfo(name=COL_STOP_GTFS)
++ val stopGtfsId: String,
++ @ColumnInfo(name=COL_ORDER)
++ val order: Int,
++){
++ companion object{
++ const val TABLE_NAME="patterns_stops"
++
++ const val COL_PATTERN_ID="pattern_gtfs_id"
++ const val COL_STOP_GTFS="stop_gtfs_id"
++ const val COL_ORDER="stop_order"
++ }
++}
+\ No newline at end of file
diff --git a/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/1.json b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/1.json
new file mode 100644
--- /dev/null
+++ b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/1.json
@@ -0,0 +1,464 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "5c633aea20ff416df784e00a939d7ae5",
+ "entities": [
+ {
+ "tableName": "gtfs_calendar_dates",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `date` TEXT NOT NULL, `exception_type` INTEGER NOT NULL, PRIMARY KEY(`service_id`, `date`), FOREIGN KEY(`service_id`) REFERENCES `gtfs_calendar`(`service_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exceptionType",
+ "columnName": "exception_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "service_id",
+ "date"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "gtfs_calendar",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "service_id"
+ ],
+ "referencedColumns": [
+ "service_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "stops_gtfs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stop_id` INTEGER NOT NULL, `stop_code` TEXT NOT NULL, `stop_name` TEXT NOT NULL, `stop_desc` TEXT NOT NULL, `stop_lat` REAL NOT NULL, `stop_lon` REAL NOT NULL, `wheelchair_boarding` TEXT NOT NULL, PRIMARY KEY(`stop_id`))",
+ "fields": [
+ {
+ "fieldPath": "internalID",
+ "columnName": "stop_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gttStopID",
+ "columnName": "stop_code",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopName",
+ "columnName": "stop_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gttPlaceName",
+ "columnName": "stop_desc",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "stop_lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "stop_lon",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchair",
+ "columnName": "wheelchair_boarding",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "stop_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_calendar",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `monday` INTEGER NOT NULL, `tuesday` INTEGER NOT NULL, `wednesday` INTEGER NOT NULL, `thursday` INTEGER NOT NULL, `friday` INTEGER NOT NULL, `saturday` INTEGER NOT NULL, `sunday` INTEGER NOT NULL, `start_date` TEXT NOT NULL, `end_date` TEXT NOT NULL, PRIMARY KEY(`service_id`))",
+ "fields": [
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onMonday",
+ "columnName": "monday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onTuesday",
+ "columnName": "tuesday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onWednesday",
+ "columnName": "wednesday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onThursday",
+ "columnName": "thursday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onFriday",
+ "columnName": "friday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onSaturday",
+ "columnName": "saturday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onSunday",
+ "columnName": "sunday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "start_date",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "end_date",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "service_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "routes_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `agency_id` TEXT NOT NULL, `route_short_name` TEXT NOT NULL, `route_long_name` TEXT NOT NULL, `route_desc` TEXT NOT NULL, `route_type` TEXT NOT NULL, `route_color` TEXT NOT NULL, `route_text_color` TEXT NOT NULL, `route_sort_order` INTEGER NOT NULL, PRIMARY KEY(`route_id`))",
+ "fields": [
+ {
+ "fieldPath": "ID",
+ "columnName": "route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agencyID",
+ "columnName": "agency_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "route_short_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longName",
+ "columnName": "route_long_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "route_desc",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "route_type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "route_color",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "textColor",
+ "columnName": "route_text_color",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sortOrder",
+ "columnName": "route_sort_order",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "route_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_stop_times",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`trip_id` TEXT NOT NULL, `arrival_time` TEXT NOT NULL, `departure_time` TEXT NOT NULL, `stop_id` INTEGER NOT NULL, `stop_sequence` INTEGER NOT NULL, PRIMARY KEY(`trip_id`, `stop_id`), FOREIGN KEY(`stop_id`) REFERENCES `stops_gtfs`(`stop_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`trip_id`) REFERENCES `gtfs_trips`(`trip_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripID",
+ "columnName": "trip_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrival_time",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departure_time",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopID",
+ "columnName": "stop_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopSequence",
+ "columnName": "stop_sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "trip_id",
+ "stop_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_gtfs_stop_times_stop_id",
+ "unique": false,
+ "columnNames": [
+ "stop_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_stop_times_stop_id` ON `${TABLE_NAME}` (`stop_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "stops_gtfs",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stop_id"
+ ],
+ "referencedColumns": [
+ "stop_id"
+ ]
+ },
+ {
+ "table": "gtfs_trips",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "trip_id"
+ ],
+ "referencedColumns": [
+ "trip_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "gtfs_trips",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `service_id` TEXT NOT NULL, `trip_id` TEXT NOT NULL, `trip_headsign` TEXT NOT NULL, `direction_id` INTEGER NOT NULL, `block_id` TEXT NOT NULL, `shape_id` TEXT NOT NULL, `wheelchair_accessible` INTEGER NOT NULL, `limited_route` INTEGER NOT NULL, PRIMARY KEY(`trip_id`), FOREIGN KEY(`route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "routeID",
+ "columnName": "route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tripID",
+ "columnName": "trip_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "trip_headsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionID",
+ "columnName": "direction_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockID",
+ "columnName": "block_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeID",
+ "columnName": "shape_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isWheelchairAccess",
+ "columnName": "wheelchair_accessible",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isLimitedRoute",
+ "columnName": "limited_route",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "trip_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_gtfs_trips_route_id",
+ "unique": false,
+ "columnNames": [
+ "route_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_trips_route_id` ON `${TABLE_NAME}` (`route_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "routes_table",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "route_id"
+ ],
+ "referencedColumns": [
+ "route_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "gtfs_shapes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`shape_id` TEXT NOT NULL, `shape_pt_lat` REAL NOT NULL, `shape_pt_lon` REAL NOT NULL, `shape_pt_sequence` INTEGER NOT NULL, PRIMARY KEY(`shape_id`, `shape_pt_sequence`))",
+ "fields": [
+ {
+ "fieldPath": "shapeID",
+ "columnName": "shape_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointLat",
+ "columnName": "shape_pt_lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointLon",
+ "columnName": "shape_pt_lon",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointSequence",
+ "columnName": "shape_pt_sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "shape_id",
+ "shape_pt_sequence"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5c633aea20ff416df784e00a939d7ae5')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/2.json b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/2.json
new file mode 100644
--- /dev/null
+++ b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/2.json
@@ -0,0 +1,648 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 2,
+ "identityHash": "6d2aa826894d1e6b1429678e13b65433",
+ "entities": [
+ {
+ "tableName": "gtfs_feeds",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`feed_id` TEXT NOT NULL, PRIMARY KEY(`feed_id`))",
+ "fields": [
+ {
+ "fieldPath": "gtfsId",
+ "columnName": "feed_id",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "feed_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_agencies",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`gtfs_id` TEXT NOT NULL, `ag_name` TEXT NOT NULL, `ag_url` TEXT NOT NULL, `fare_url` TEXT, `phone` TEXT, `feed_id` TEXT, PRIMARY KEY(`gtfs_id`))",
+ "fields": [
+ {
+ "fieldPath": "gtfsId",
+ "columnName": "gtfs_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "ag_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "ag_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fareUrl",
+ "columnName": "fare_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "phone",
+ "columnName": "phone",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "feed.gtfsId",
+ "columnName": "feed_id",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "gtfs_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_calendar_dates",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `date` TEXT NOT NULL, `exception_type` INTEGER NOT NULL, PRIMARY KEY(`service_id`, `date`), FOREIGN KEY(`service_id`) REFERENCES `gtfs_calendar`(`service_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exceptionType",
+ "columnName": "exception_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "service_id",
+ "date"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "gtfs_calendar",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "service_id"
+ ],
+ "referencedColumns": [
+ "service_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "stops_gtfs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stop_id` INTEGER NOT NULL, `stop_code` TEXT NOT NULL, `stop_name` TEXT NOT NULL, `stop_desc` TEXT NOT NULL, `stop_lat` REAL NOT NULL, `stop_lon` REAL NOT NULL, `wheelchair_boarding` TEXT NOT NULL, PRIMARY KEY(`stop_id`))",
+ "fields": [
+ {
+ "fieldPath": "internalID",
+ "columnName": "stop_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gttStopID",
+ "columnName": "stop_code",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopName",
+ "columnName": "stop_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gttPlaceName",
+ "columnName": "stop_desc",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "stop_lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "stop_lon",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchair",
+ "columnName": "wheelchair_boarding",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "stop_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_calendar",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `monday` INTEGER NOT NULL, `tuesday` INTEGER NOT NULL, `wednesday` INTEGER NOT NULL, `thursday` INTEGER NOT NULL, `friday` INTEGER NOT NULL, `saturday` INTEGER NOT NULL, `sunday` INTEGER NOT NULL, `start_date` TEXT NOT NULL, `end_date` TEXT NOT NULL, PRIMARY KEY(`service_id`))",
+ "fields": [
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onMonday",
+ "columnName": "monday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onTuesday",
+ "columnName": "tuesday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onWednesday",
+ "columnName": "wednesday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onThursday",
+ "columnName": "thursday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onFriday",
+ "columnName": "friday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onSaturday",
+ "columnName": "saturday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onSunday",
+ "columnName": "sunday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "start_date",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "end_date",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "service_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "routes_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `agency_id` TEXT NOT NULL, `route_short_name` TEXT NOT NULL, `route_long_name` TEXT NOT NULL, `route_desc` TEXT NOT NULL, `route_mode` TEXT NOT NULL, `route_color` TEXT NOT NULL, `route_text_color` TEXT NOT NULL, PRIMARY KEY(`route_id`))",
+ "fields": [
+ {
+ "fieldPath": "gtfsId",
+ "columnName": "route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agencyID",
+ "columnName": "agency_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "route_short_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longName",
+ "columnName": "route_long_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "route_desc",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mode",
+ "columnName": "route_mode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "route_color",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "textColor",
+ "columnName": "route_text_color",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "route_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_stop_times",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`trip_id` TEXT NOT NULL, `arrival_time` TEXT NOT NULL, `departure_time` TEXT NOT NULL, `stop_id` INTEGER NOT NULL, `stop_sequence` INTEGER NOT NULL, PRIMARY KEY(`trip_id`, `stop_id`), FOREIGN KEY(`stop_id`) REFERENCES `stops_gtfs`(`stop_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`trip_id`) REFERENCES `gtfs_trips`(`trip_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripID",
+ "columnName": "trip_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrival_time",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departure_time",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopID",
+ "columnName": "stop_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopSequence",
+ "columnName": "stop_sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "trip_id",
+ "stop_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_gtfs_stop_times_stop_id",
+ "unique": false,
+ "columnNames": [
+ "stop_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_stop_times_stop_id` ON `${TABLE_NAME}` (`stop_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "stops_gtfs",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stop_id"
+ ],
+ "referencedColumns": [
+ "stop_id"
+ ]
+ },
+ {
+ "table": "gtfs_trips",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "trip_id"
+ ],
+ "referencedColumns": [
+ "trip_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "gtfs_trips",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `service_id` TEXT NOT NULL, `trip_id` TEXT NOT NULL, `trip_headsign` TEXT NOT NULL, `direction_id` INTEGER NOT NULL, `block_id` TEXT NOT NULL, `shape_id` TEXT NOT NULL, `wheelchair_accessible` INTEGER NOT NULL, `limited_route` INTEGER NOT NULL, PRIMARY KEY(`trip_id`), FOREIGN KEY(`route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "routeID",
+ "columnName": "route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tripID",
+ "columnName": "trip_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "trip_headsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionID",
+ "columnName": "direction_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockID",
+ "columnName": "block_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeID",
+ "columnName": "shape_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isWheelchairAccess",
+ "columnName": "wheelchair_accessible",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isLimitedRoute",
+ "columnName": "limited_route",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "trip_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_gtfs_trips_route_id",
+ "unique": false,
+ "columnNames": [
+ "route_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_trips_route_id` ON `${TABLE_NAME}` (`route_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "routes_table",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "route_id"
+ ],
+ "referencedColumns": [
+ "route_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "gtfs_shapes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`shape_id` TEXT NOT NULL, `shape_pt_lat` REAL NOT NULL, `shape_pt_lon` REAL NOT NULL, `shape_pt_sequence` INTEGER NOT NULL, PRIMARY KEY(`shape_id`, `shape_pt_sequence`))",
+ "fields": [
+ {
+ "fieldPath": "shapeID",
+ "columnName": "shape_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointLat",
+ "columnName": "shape_pt_lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointLon",
+ "columnName": "shape_pt_lon",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointSequence",
+ "columnName": "shape_pt_sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "shape_id",
+ "shape_pt_sequence"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "mato_patterns",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pattern_name` TEXT NOT NULL, `pattern_code` TEXT NOT NULL, `pattern_hash` TEXT NOT NULL, `pattern_direction_id` INTEGER NOT NULL, `pattern_route_id` TEXT NOT NULL, `pattern_headsign` TEXT, `pattern_polyline` TEXT NOT NULL, `pattern_polylength` INTEGER NOT NULL, PRIMARY KEY(`pattern_code`), FOREIGN KEY(`pattern_route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "name",
+ "columnName": "pattern_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "code",
+ "columnName": "pattern_code",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semanticHash",
+ "columnName": "pattern_hash",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "pattern_direction_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeGtfsId",
+ "columnName": "pattern_route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "pattern_headsign",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "patternGeometryPoly",
+ "columnName": "pattern_polyline",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "patternGeometryLength",
+ "columnName": "pattern_polylength",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "pattern_code"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "routes_table",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "pattern_route_id"
+ ],
+ "referencedColumns": [
+ "route_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "patterns_stops",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pattern_gtfs_id` TEXT NOT NULL, `stop_gtfs_id` TEXT NOT NULL, `stop_order` INTEGER NOT NULL, PRIMARY KEY(`pattern_gtfs_id`, `stop_gtfs_id`, `stop_order`), FOREIGN KEY(`pattern_gtfs_id`) REFERENCES `mato_patterns`(`pattern_code`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "patternId",
+ "columnName": "pattern_gtfs_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopGtfsId",
+ "columnName": "stop_gtfs_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "stop_order",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "pattern_gtfs_id",
+ "stop_gtfs_id",
+ "stop_order"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "mato_patterns",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "pattern_gtfs_id"
+ ],
+ "referencedColumns": [
+ "pattern_code"
+ ]
+ }
+ ]
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6d2aa826894d1e6b1429678e13b65433')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -7,6 +7,8 @@
}
ext {
+ androidXTestVersion = "1.4.0"
+
//multidex
multidex_version = "2.0.1"
//libraries versions
@@ -57,6 +59,12 @@
versionName "1.16.3"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments = ["room.schemaLocation": "$projectDir/assets/schemas/".toString()]
+ }
+ }
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
@@ -65,6 +73,8 @@
}
sourceSets {
+ androidTest.assets.srcDirs += files("$projectDir/assets/schemas/".toString())
+
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
@@ -128,17 +138,34 @@
// Room components
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
- androidTestImplementation "androidx.room:room-testing:$room_version"
//multidex - we need this to build the app
implementation "androidx.multidex:multidex:$multidex_version"
implementation 'de.siegmar:fastcsv:2.0.0'
+ testImplementation 'junit:junit:4.12'
+ implementation 'junit:junit:4.12'
+
+ implementation "androidx.test.ext:junit:1.1.3"
+ implementation "androidx.test:core:$androidXTestVersion"
+ implementation "androidx.test:runner:$androidXTestVersion"
+ implementation "androidx.room:room-testing:$room_version"
+
+ androidTestImplementation "androidx.test.ext:junit:1.1.3"
+ androidTestImplementation "androidx.test:core:$androidXTestVersion"
+ androidTestImplementation "androidx.test:runner:$androidXTestVersion"
+ androidTestImplementation "androidx.test:rules:$androidXTestVersion"
+ androidTestImplementation "androidx.room:room-testing:$room_version"
+
+
+
+
}
}
dependencies {
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
diff --git a/res/layout/arrivals_nearby_card.xml b/res/layout/arrivals_nearby_card.xml
--- a/res/layout/arrivals_nearby_card.xml
+++ b/res/layout/arrivals_nearby_card.xml
@@ -27,16 +27,23 @@
android:layout_margin="10dp"
android:textStyle="normal" android:layout_marginRight="20dp" android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"/>
- <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
- android:id="@+id/lineDirectionTextView"
- android:text="piazza statuto" android:textSize="20sp"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:layout_toLeftOf="@id/arrivalsDistanceTextView"
- android:layout_toRightOf="@id/lineNameTextView"
- android:textStyle="normal" android:layout_marginRight="6dp" android:layout_marginEnd="6dp"
- android:layout_alignBaseline="@id/lineNameTextView"
- android:layout_marginLeft="10dp" android:layout_marginTop="10dp"
- android:layout_marginStart="10dp"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/lineDirectionTextView"
+ android:text="piazza statuto"
+ android:textSize="20sp"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_toLeftOf="@id/arrivalsDistanceTextView"
+ android:layout_toRightOf="@id/lineNameTextView"
+ android:textStyle="normal"
+ android:layout_marginRight="6dp"
+ android:layout_marginEnd="6dp"
+ android:layout_alignBaseline="@id/lineNameTextView"
+ android:layout_marginLeft="10dp"
+ android:layout_marginTop="5dp"
+ android:layout_marginStart="10dp" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_alignBaseline="@id/arrivalsTimeTextView"
android:textAppearance="?android:attr/textAppearanceMedium"
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -95,7 +95,9 @@
<string name="position_searching_message">Ricerca della posizione in corso&#8230;</string>
<string name="no_stops_nearby">Nessuna fermata nei dintorni</string>
<string name="main_menu_pref">Preferenze</string>
- <string name="database_update_message">Aggiornamento del database&#8230;</string>
+ <string name="database_update_msg_inapp">Aggiornamento del database&#8230;</string>
+ <string name="database_update_msg_notif">Aggiornamento del database</string>
+
<string name="pref_num_elements">Numero minimo di fermate</string>
<string name="num_stops_nearby_not_number">Il numero di fermate da ricercare non è valido</string>
<string name="invalid_number">Valore errato, inserisci un numero</string>
@@ -140,6 +142,10 @@
<string name="default_notification_channel_description">Canale unico delle notifiche</string>
+ <string name="database_notification_channel">Database</string>
+ <string name="database_notification_channel_desc">Informazioni sul database (aggiornamento)</string>
+
+
<string name="too_many_permission_asks">Chiesto troppe volte per il permesso %1$s</string>
<string name="permission_storage_maps_msg">Non si può usare questa funzionalità senza il permesso di archivio</string>
<string name="storage_permission">di archivio</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -126,7 +126,8 @@
<string name="enable_position_message_map">Allow access to position to show it on the map</string>
<string name="enableGpsText">Please enable GPS</string>
- <string name="database_update_message">Database update in progress&#8230;</string>
+ <string name="database_update_msg_inapp">Database update in progress&#8230;</string>
+ <string name="database_update_msg_notif">Updating the database</string>
<string name="bus_arriving_at">is arriving at</string>
<string name="arrivals_card_at_the_stop">at the stop</string>
<string name="two_strings_format" translatable="false">%1$s - %2$s</string>
@@ -156,6 +157,9 @@
-->
<string name="default_notification_channel" translatable="false">Default</string>
<string name="default_notification_channel_description">Default channel for notifications</string>
+ <string name="database_notification_channel">Database</string>
+ <string name="database_notification_channel_desc">Notifications on the update of the database</string>
+
<string name="too_many_permission_asks">Asked for %1$s permission too many times</string>
<string name="permission_storage_maps_msg">Cannot use the map with the storage permission!</string>
<string name="storage_permission">storage</string>
diff --git a/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java b/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java
new file mode 100644
--- /dev/null
+++ b/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java
@@ -0,0 +1,53 @@
+package it.reyboz.bustorino.data.gtfs;
+
+import androidx.room.Room;
+import androidx.room.migration.Migration;
+import androidx.room.testing.MigrationTestHelper;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
+
+//@RunWith(AndroidJUnit4.class)
+public class GtfsDBMigrationsTest {
+ private static final String TEST_DB = "migration-test";
+
+ @Rule
+ public MigrationTestHelper helper;
+
+ public GtfsDBMigrationsTest() {
+ helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
+ GtfsDatabase.class.getCanonicalName(),
+ new FrameworkSQLiteOpenHelperFactory());
+ }
+
+ @Test
+ public void migrateAll() throws IOException {
+ // Create earliest version of the database.
+ SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
+ db.close();
+
+ // Open latest version of the database. Room will validate the schema
+ // once all migrations execute.
+ GtfsDatabase appDb = Room.databaseBuilder(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(),
+ GtfsDatabase.class,
+ TEST_DB)
+ .addMigrations(ALL_MIGRATIONS).build();
+ appDb.getOpenHelper().getWritableDatabase();
+ appDb.close();
+ }
+
+ // Array of all migrations
+ private static final Migration[] ALL_MIGRATIONS = new Migration[]{
+ GtfsDatabase.Companion.getMIGRATION_1_2()};
+}
+
diff --git a/src/it/reyboz/bustorino/ActivityExperiments.java b/src/it/reyboz/bustorino/ActivityExperiments.java
--- a/src/it/reyboz/bustorino/ActivityExperiments.java
+++ b/src/it/reyboz/bustorino/ActivityExperiments.java
@@ -18,21 +18,16 @@
package it.reyboz.bustorino;
import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
-import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.gtfs.GtfsDataParser;
import it.reyboz.bustorino.backend.networkTools;
-import it.reyboz.bustorino.backend.utils;
import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
-import it.reyboz.bustorino.data.gtfs.StaticGtfsDao;
+import it.reyboz.bustorino.data.gtfs.GtfsDBDao;
import it.reyboz.bustorino.middleware.GeneralActivity;
import java.io.*;
@@ -44,7 +39,6 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
public class ActivityExperiments extends GeneralActivity {
@@ -91,7 +85,7 @@
"ExperimentGTFS", "Last update date is " + updateDate//utils.joinList(files, "\n")
);
//Toast.makeText(v.getContext(), "Gtfs data already downloaded", Toast.LENGTH_SHORT).show();
- StaticGtfsDao dao = GtfsDatabase.Companion.getGtfsDatabase(appContext).gtfsDao();
+ GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(appContext).gtfsDao();
Log.d(DEBUG_TAG, String.valueOf(dao));
dao.deleteAllStopTimes();
@@ -180,7 +174,7 @@
Runnable deleteDB = new Runnable() {
@Override
public void run() {
- StaticGtfsDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
+ GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
Log.d(DEBUG_TAG, String.valueOf(dao));
dao.deleteAllStopTimes();
dao.deleteAllTrips();
diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java
--- a/src/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/src/it/reyboz/bustorino/ActivityPrincipal.java
@@ -41,11 +41,6 @@
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager;
-import androidx.work.BackoffPolicy;
-import androidx.work.Constraints;
-import androidx.work.ExistingPeriodicWorkPolicy;
-import androidx.work.NetworkType;
-import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
@@ -53,7 +48,6 @@
import com.google.android.material.snackbar.Snackbar;
import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.DBUpdateWorker;
@@ -389,7 +383,7 @@
}
if (baseView == null) baseView = findViewById(R.id.mainActContentFrame);
if (baseView == null) Log.e(DEBUG_TAG, "baseView null for default snackbar, probably exploding now");
- snackbar = Snackbar.make(baseView, R.string.database_update_message, Snackbar.LENGTH_INDEFINITE);
+ snackbar = Snackbar.make(baseView, R.string.database_update_msg_inapp, Snackbar.LENGTH_INDEFINITE);
snackbar.show();
}
diff --git a/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java b/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
--- a/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
@@ -18,10 +18,12 @@
package it.reyboz.bustorino.adapters;
import android.content.Context;
+import android.content.SharedPreferences;
import android.location.Location;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
+import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
@@ -37,15 +39,18 @@
import java.util.*;
-public class ArrivalsStopAdapter extends RecyclerView.Adapter<ArrivalsStopAdapter.ViewHolder> {
+public class ArrivalsStopAdapter extends RecyclerView.Adapter<ArrivalsStopAdapter.ViewHolder> implements SharedPreferences.OnSharedPreferenceChangeListener {
private final static int layoutRes = R.layout.arrivals_nearby_card;
//private List<Stop> stops;
private @Nullable Location userPosition;
private FragmentListenerMain listener;
- private List< Pair<Stop, Route> > routesPairList = new ArrayList<>();
+ private List< Pair<Stop, Route> > routesPairList;
private final Context context;
//Maximum number of stops to keep
private final int MAX_STOPS = 20; //TODO: make it programmable
+ private String KEY_CAPITALIZE;
+ private NameCapitalize capit;
+
public ArrivalsStopAdapter(@Nullable List< Pair<Stop, Route> > routesPairList, FragmentListenerMain fragmentListener, Context con, @Nullable Location pos) {
listener = fragmentListener;
@@ -55,10 +60,16 @@
resetListAndPosition();
// if(paline!=null)
//resetRoutesPairList(paline);
+ KEY_CAPITALIZE = context.getString(R.string.pref_arrival_times_capit);
+ SharedPreferences defSharPref = PreferenceManager.getDefaultSharedPreferences(context);
+ defSharPref.registerOnSharedPreferenceChangeListener(this);
+ String capitalizeKey = defSharPref.getString(KEY_CAPITALIZE, "");
+ this.capit = NameCapitalize.getCapitalize(capitalizeKey);
}
+ @NonNull
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false);
@@ -85,7 +96,7 @@
//final String routeName = String.format(context.getResources().getString(R.string.two_strings_format),r.getNameForDisplay(),r.destinazione);
if (r!=null) {
holder.lineNameTextView.setText(r.getNameForDisplay());
- holder.lineDirectionTextView.setText(r.destinazione);
+ holder.lineDirectionTextView.setText(NameCapitalize.capitalizePass(r.destinazione, capit));
holder.arrivalsTextView.setText(r.getPassaggiToString(0,2,true));
} else {
holder.lineNameTextView.setVisibility(View.INVISIBLE);
@@ -117,6 +128,17 @@
return routesPairList.size();
}
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if(key.equals(KEY_CAPITALIZE)){
+ String k = sharedPreferences.getString(KEY_CAPITALIZE, "");
+ capit = NameCapitalize.getCapitalize(k);
+
+ notifyDataSetChanged();
+
+ }
+ }
+
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView lineNameTextView;
TextView lineDirectionTextView;
diff --git a/src/it/reyboz/bustorino/adapters/NameCapitalize.java b/src/it/reyboz/bustorino/adapters/NameCapitalize.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/adapters/NameCapitalize.java
@@ -0,0 +1,38 @@
+package it.reyboz.bustorino.adapters;
+
+import java.util.Locale;
+
+import it.reyboz.bustorino.backend.utils;
+
+public enum NameCapitalize {
+ DO_NOTHING, ALL, FIRST;
+ public static NameCapitalize getCapitalize(String capitalize){
+
+ switch (capitalize.trim()){
+ case "KEEP":
+ return NameCapitalize.DO_NOTHING;
+ case "CAPITALIZE_ALL":
+ return NameCapitalize.ALL;
+
+ case "CAPITALIZE_FIRST":
+ return NameCapitalize.FIRST;
+ }
+ return NameCapitalize.DO_NOTHING;
+ }
+
+ public static String capitalizePass(String input, NameCapitalize capitalize){
+ String dest = input;
+ switch (capitalize){
+ case ALL:
+ dest = input.toUpperCase(Locale.ROOT);
+ break;
+ case FIRST:
+ dest = utils.toTitleCase(input, true);
+ break;
+ case DO_NOTHING:
+ default:
+
+ }
+ return dest;
+ }
+}
diff --git a/src/it/reyboz/bustorino/backend/Notifications.java b/src/it/reyboz/bustorino/backend/Notifications.java
--- a/src/it/reyboz/bustorino/backend/Notifications.java
+++ b/src/it/reyboz/bustorino/backend/Notifications.java
@@ -8,6 +8,7 @@
public class Notifications {
public static final String DEFAULT_CHANNEL_ID ="Default";
+ public static final String DB_UPDATE_CHANNELS_ID ="Database Update";
public static void createDefaultNotificationChannel(Context context) {
// Create the NotificationChannel, but only on API 26+ because
diff --git a/src/it/reyboz/bustorino/backend/gtfs/PolylineParser.java b/src/it/reyboz/bustorino/backend/gtfs/PolylineParser.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/gtfs/PolylineParser.java
@@ -0,0 +1,48 @@
+package it.reyboz.bustorino.backend.gtfs;
+
+import org.osmdroid.util.GeoPoint;
+
+import java.util.ArrayList;
+
+public final class PolylineParser {
+ /**
+ * Decode a Google polyline
+ * Thanks to https://stackoverflow.com/questions/9341020/how-to-decode-googles-polyline-algorithm
+ * @param encodedPolyline the encoded polyline in a string
+ * @param initial_capacity for the list
+ * @return the list of points correspoding to the polyline
+ */
+ public static ArrayList<GeoPoint> decodePolyline(String encodedPolyline, int initial_capacity) {
+ ArrayList<GeoPoint> points = new ArrayList<>(initial_capacity);
+ int truck = 0;
+ int carriage_q = 0;
+ int longit=0, latit=0;
+ boolean is_lat=true;
+ for (int x = 0, xx = encodedPolyline.length(); x < xx; ++x) {
+ int i = encodedPolyline.charAt(x);
+ i -= 63;
+ int _5_bits = i << (32 - 5) >>> (32 - 5);
+ truck |= _5_bits << carriage_q;
+ carriage_q += 5;
+ boolean is_last = (i & (1 << 5)) == 0;
+ if (is_last) {
+ boolean is_negative = (truck & 1) == 1;
+ truck >>>= 1;
+ if (is_negative) {
+ truck = ~truck;
+ }
+ if (is_lat){
+ latit += truck;
+ is_lat = false;
+ } else{
+ longit += truck;
+ points.add(new GeoPoint((double)latit/1e5,(double)longit/1e5));
+ is_lat=true;
+ }
+ carriage_q = 0;
+ truck = 0;
+ }
+ }
+ return points;
+ }
+}
diff --git a/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java b/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java
--- a/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java
+++ b/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java
@@ -31,10 +31,7 @@
import org.json.JSONException;
import org.json.JSONObject;
-import java.nio.charset.StandardCharsets;
import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import it.reyboz.bustorino.backend.Fetcher;
@@ -54,7 +51,7 @@
AtomicReference<Fetcher.Result> res,
Response.Listener<Palina> listener,
@Nullable Response.ErrorListener errorListener) {
- super(MatoAPIFetcher.QueryType.ARRIVALS, listener, errorListener);
+ super(MatoQueries.QueryType.ARRIVALS, listener, errorListener);
this.stopName = stopName;
this.startingTime = startingTime;
this.timeRange = timeRange;
diff --git a/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java b/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java
--- a/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java
+++ b/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java
@@ -12,9 +12,9 @@
private static final String API_URL="https://mapi.5t.torino.it/routing/v1/routers/mat/index/graphql";
protected final Response.Listener<T> listener;
- private final MatoAPIFetcher.QueryType type;
+ protected final MatoQueries.QueryType type;
public MapiVolleyRequest(
- MatoAPIFetcher.QueryType type,
+ MatoQueries.QueryType type,
Response.Listener<T> listener,
@Nullable Response.ErrorListener errorListener) {
super(Method.POST, API_URL, errorListener);
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
--- a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
+++ b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
@@ -19,15 +19,23 @@
import android.content.Context
import android.util.Log
+import com.android.volley.DefaultRetryPolicy
import com.android.volley.toolbox.RequestFuture
import it.reyboz.bustorino.BuildConfig
import it.reyboz.bustorino.backend.*
+import it.reyboz.bustorino.data.gtfs.GtfsAgency
+import it.reyboz.bustorino.data.gtfs.GtfsFeed
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.data.gtfs.MatoPattern
+import org.json.JSONArray
+import org.json.JSONException
import org.json.JSONObject
import java.util.*
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicReference
+import kotlin.collections.ArrayList
open class MatoAPIFetcher(val minNumPassaggi: Int) : ArrivalsFetcher {
@@ -58,7 +66,7 @@
return Palina(stopID)
}
val requestQueue = NetworkVolleyManager.getInstance(appContext).requestQueue
- request.setTag(getVolleyReqTag(QueryType.ARRIVALS))
+ request.setTag(getVolleyReqTag(MatoQueries.QueryType.ARRIVALS))
requestQueue.add(request)
try {
@@ -105,10 +113,15 @@
"DNT" to "1",
"Host" to "mapi.5t.torino.it")
- fun getVolleyReqTag(type: QueryType): String{
+ private val longRetryPolicy = DefaultRetryPolicy(10000,5,DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)
+
+ fun getVolleyReqTag(type: MatoQueries.QueryType): String{
return when (type){
- QueryType.ALL_STOPS -> VOLLEY_TAG +"_AllStops"
- QueryType.ARRIVALS -> VOLLEY_TAG+"_Arrivals"
+ MatoQueries.QueryType.ALL_STOPS -> VOLLEY_TAG +"_AllStops"
+ MatoQueries.QueryType.ARRIVALS -> VOLLEY_TAG+"_Arrivals"
+ MatoQueries.QueryType.FEEDS -> VOLLEY_TAG +"_Feeds"
+ MatoQueries.QueryType.ROUTES -> VOLLEY_TAG +"_AllRoutes"
+ MatoQueries.QueryType.PATTERNS_FOR_ROUTES -> VOLLEY_TAG + "_PatternsForRoute"
}
}
@@ -120,14 +133,15 @@
val future = RequestFuture.newFuture<List<Palina>>()
val request = VolleyAllStopsRequest(future, future)
- request.tag = getVolleyReqTag(QueryType.ALL_STOPS)
+ request.tag = getVolleyReqTag(MatoQueries.QueryType.ALL_STOPS)
+ request.retryPolicy = longRetryPolicy
requestQueue.add(request)
var palinaList:List<Palina> = mutableListOf()
try {
- palinaList = future.get(60, TimeUnit.SECONDS)
+ palinaList = future.get(120, TimeUnit.SECONDS)
res?.set(Fetcher.Result.OK)
}catch (e: InterruptedException) {
@@ -259,10 +273,159 @@
return data
}
- }
- enum class QueryType {
- ARRIVALS, ALL_STOPS
+ fun getFeedsAndAgencies(context: Context, res: AtomicReference<Fetcher.Result>?):
+ Pair<List<GtfsFeed>, ArrayList<GtfsAgency>> {
+ val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
+ val future = RequestFuture.newFuture<JSONObject>()
+
+ val request = MatoVolleyJSONRequest(MatoQueries.QueryType.FEEDS, JSONObject(), future, future)
+ request.setRetryPolicy(longRetryPolicy)
+ request.tag = getVolleyReqTag(MatoQueries.QueryType.FEEDS)
+
+ requestQueue.add(request)
+
+ val feeds = ArrayList<GtfsFeed>()
+ val agencies = ArrayList<GtfsAgency>()
+ var outObj = ""
+ try {
+ val resObj = future.get(120,TimeUnit.SECONDS)
+ outObj = resObj.toString(1)
+ val feedsJSON = resObj.getJSONArray("feeds")
+ for (i in 0 until feedsJSON.length()){
+ val resTup = ResponseParsing.parseFeedJSON(feedsJSON.getJSONObject(i))
+ feeds.add(resTup.first)
+
+ agencies.addAll(resTup.second)
+ }
+
+
+ } catch (e: InterruptedException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ } catch (e: ExecutionException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.SERVER_ERROR)
+ } catch (e: TimeoutException) {
+ res?.set(Fetcher.Result.CONNECTION_ERROR)
+ e.printStackTrace()
+ } catch (e: JSONException){
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+ }
+ return Pair(feeds,agencies)
+
+ }
+ fun getRoutes(context: Context, res: AtomicReference<Fetcher.Result>?):
+ ArrayList<GtfsRoute>{
+ val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
+ val future = RequestFuture.newFuture<JSONObject>()
+
+ val params = JSONObject()
+ params.put("feeds","gtt")
+
+ val request = MatoVolleyJSONRequest(MatoQueries.QueryType.ROUTES, params, future, future)
+ request.tag = getVolleyReqTag(MatoQueries.QueryType.ROUTES)
+ request.retryPolicy = longRetryPolicy
+
+ requestQueue.add(request)
+
+ val routes = ArrayList<GtfsRoute>()
+ var outObj = ""
+ try {
+ val resObj = future.get(120,TimeUnit.SECONDS)
+ outObj = resObj.toString(1)
+ val routesJSON = resObj.getJSONArray("routes")
+ for (i in 0 until routesJSON.length()){
+ val route = ResponseParsing.parseRouteJSON(routesJSON.getJSONObject(i))
+ routes.add(route)
+ }
+
+
+ } catch (e: InterruptedException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ } catch (e: ExecutionException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.SERVER_ERROR)
+ } catch (e: TimeoutException) {
+ res?.set(Fetcher.Result.CONNECTION_ERROR)
+ e.printStackTrace()
+ } catch (e: JSONException){
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+ }
+ return routes
+
+ }
+ fun getPatternsWithStops(context: Context, routesGTFSIds: ArrayList<String>, res: AtomicReference<Fetcher.Result>?): ArrayList<MatoPattern>{
+ val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
+
+ val future = RequestFuture.newFuture<JSONObject>()
+
+ val params = JSONObject()
+ for (r in routesGTFSIds){
+ if(r.isEmpty()) routesGTFSIds.remove(r)
+ }
+ val routes = JSONArray(routesGTFSIds)
+
+ params.put("routes",routes)
+
+ val request = MatoVolleyJSONRequest(MatoQueries.QueryType.PATTERNS_FOR_ROUTES, params, future, future)
+ request.retryPolicy = longRetryPolicy
+ request.tag = getVolleyReqTag(MatoQueries.QueryType.PATTERNS_FOR_ROUTES)
+
+ requestQueue.add(request)
+
+ val patterns = ArrayList<MatoPattern>()
+ //var outObj = ""
+ try {
+ val resObj = future.get(60,TimeUnit.SECONDS)
+ //outObj = resObj.toString(1)
+ val routesJSON = resObj.getJSONArray("routes")
+ for (i in 0 until routesJSON.length()){
+ val patternList = ResponseParsing.parseRoutePatternsStopsJSON(routesJSON.getJSONObject(i))
+ patterns.addAll(patternList)
+ }
+
+
+ } catch (e: InterruptedException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ } catch (e: ExecutionException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.SERVER_ERROR)
+ } catch (e: TimeoutException) {
+ res?.set(Fetcher.Result.CONNECTION_ERROR)
+ e.printStackTrace()
+ } catch (e: JSONException){
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ //Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+ }
+ /*
+ var numRequests = 0
+ for(routeName in routesGTFSIds){
+ if (!routeName.isEmpty()) numRequests++
+ }
+ val countDownForRequests = CountDownLatch(numRequests)
+ val lockSave = ReentrantLock()
+ //val countDownFor
+ for (routeName in routesGTFSIds){
+ val pars = JSONObject()
+ pars.put("")
+
+ }
+ val goodResponseListener = Response.Listener<JSONObject> { }
+ val errorResponseListener = Response.ErrorListener { }
+ */
+
+ return patterns
+ }
+
+
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt b/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt
--- a/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt
+++ b/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt
@@ -86,5 +86,81 @@
}
}
"""
+
+ const val ALL_FEEDS="""
+ query AllFeeds{
+ feeds{
+ feedId
+ agencies{
+ gtfsId
+ name
+ url
+ fareUrl
+ phone
+ }
+ }
+ }
+ """
+
+ const val ROUTES_BY_FEED="""
+ query AllRoutes(${'$'}feeds: [String]){
+ routes(feeds: ${'$'}feeds) {
+ agency{
+ gtfsId
+ }
+ gtfsId
+ shortName
+ longName
+ type
+ desc
+ color
+ textColor
+ }
+ }
+ """
+
+ const val ROUTES_WITH_PATTERNS="""
+ query RoutesWithPatterns(${'$'}routes: [String]) {
+ routes(ids: ${'$'}routes) {
+ gtfsId
+ shortName
+ longName
+ type
+
+ patterns{
+ name
+ code
+ semanticHash
+ directionId
+ headsign
+ stops{
+ gtfsId
+ lat
+ lon
+ }
+ patternGeometry{
+ length
+ points
+ }
+
+ }
+ }
+ }
+ """
+
+ fun getNameAndRequest(type: QueryType): Pair<String, String>{
+ return when (type){
+ QueryType.FEEDS -> Pair("AllFeeds", ALL_FEEDS)
+ QueryType.ALL_STOPS -> Pair("AllStops", ALL_STOPS_BY_FEEDS)
+ QueryType.ARRIVALS -> Pair("AllStopsDirect", QUERY_ARRIVALS)
+ QueryType.ROUTES -> Pair("AllRoutes", ROUTES_BY_FEED)
+ QueryType.PATTERNS_FOR_ROUTES -> Pair("RoutesWithPatterns", ROUTES_WITH_PATTERNS)
+ }
+ }
}
+
+ enum class QueryType {
+ ARRIVALS, ALL_STOPS, FEEDS, ROUTES, PATTERNS_FOR_ROUTES
+ }
+
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoVolleyJSONRequest.kt b/src/it/reyboz/bustorino/backend/mato/MatoVolleyJSONRequest.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/mato/MatoVolleyJSONRequest.kt
@@ -0,0 +1,48 @@
+package it.reyboz.bustorino.backend.mato
+
+import android.util.Log
+import com.android.volley.NetworkResponse
+import com.android.volley.Response
+import com.android.volley.VolleyError
+import com.android.volley.toolbox.HttpHeaderParser
+import org.json.JSONException
+import org.json.JSONObject
+
+class MatoVolleyJSONRequest(type: MatoQueries.QueryType,
+ val variables: JSONObject,
+ listener: Response.Listener<JSONObject>,
+ errorListener: Response.ErrorListener?)
+ : MapiVolleyRequest<JSONObject>(type, listener, errorListener) {
+ protected val requestName:String
+ protected val requestQuery:String
+ init {
+ val dd = MatoQueries.getNameAndRequest(type)
+ requestName = dd.first
+ requestQuery = dd.second
+ }
+
+ override fun getBody(): ByteArray {
+
+ val data = MatoAPIFetcher.makeRequestParameters(requestName, variables, requestQuery)
+
+ return data.toString().toByteArray()
+ }
+
+ override fun parseNetworkResponse(response: NetworkResponse?): Response<JSONObject> {
+ if (response==null)
+ return Response.error(VolleyError("Null response"))
+ else if(response.statusCode != 200)
+ return Response.error(VolleyError("Response not ready, status "+response.statusCode))
+ val obj:JSONObject
+ try {
+ obj = JSONObject(String(response.data)).getJSONObject("data")
+ }catch (ex: JSONException){
+ Log.e("BusTO-VolleyJSON","Cannot parse response as JSON")
+ ex.printStackTrace()
+ return Response.error(VolleyError("Error parsing JSON"))
+ }
+
+ return Response.success(obj, HttpHeaderParser.parseCacheHeaders(response))
+ }
+
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/ResponseParsing.kt b/src/it/reyboz/bustorino/backend/mato/ResponseParsing.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/mato/ResponseParsing.kt
@@ -0,0 +1,120 @@
+/*
+ BusTO - Backend components
+ Copyright (C) 2022 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.backend.mato
+
+import it.reyboz.bustorino.data.gtfs.*
+import org.json.JSONObject
+
+abstract class ResponseParsing{
+
+ companion object{
+ fun parseAgencyJSON(jsonObject: JSONObject): GtfsAgency {
+ return GtfsAgency(
+ jsonObject.getString("gtfsId"),
+ jsonObject.getString("name"),
+ jsonObject.getString("url"),
+ jsonObject.getString("fareUrl"),
+ jsonObject.getString("phone"),
+ null
+ )
+ }
+
+ /**
+ * Parse a feed request json, containing the GTFS agencies it is served by
+ */
+ fun parseFeedJSON(jsonObject: JSONObject): Pair<GtfsFeed, ArrayList<GtfsAgency>> {
+
+ val agencies = ArrayList<GtfsAgency>()
+ val feed = GtfsFeed(jsonObject.getString("feedId"))
+ val oo = jsonObject.getJSONArray("agencies")
+ agencies.ensureCapacity(oo.length())
+ for (i in 0 until oo.length()){
+ val agObj = oo.getJSONObject(i)
+
+ agencies.add(
+ GtfsAgency(
+ agObj.getString("gtfsId"),
+ agObj.getString("name"),
+ agObj.getString("url"),
+ agObj.getString("fareUrl"),
+ agObj.getString("phone"),
+ feed
+ )
+ )
+ }
+ return Pair(feed, agencies)
+ }
+
+ fun parseRouteJSON(jsonObject: JSONObject): GtfsRoute {
+
+ val agencyJSON = jsonObject.getJSONObject("agency")
+ val agencyId = agencyJSON.getString("gtfsId")
+
+
+ return GtfsRoute(
+ jsonObject.getString("gtfsId"),
+ agencyId,
+ jsonObject.getString("shortName"),
+ jsonObject.getString("longName"),
+ jsonObject.getString("desc"),
+ GtfsMode.getByValue(jsonObject.getInt("type"))!!,
+ jsonObject.getString("color"),
+ jsonObject.getString("textColor")
+
+ )
+ }
+
+ /**
+ * Parse a route pattern from the JSON response of the MaTO server
+ */
+ fun parseRoutePatternsStopsJSON(jsonObject: JSONObject) : ArrayList<MatoPattern>{
+ val routeGtfsId = jsonObject.getString("gtfsId")
+
+ val patternsJSON = jsonObject.getJSONArray("patterns")
+ val patternsOut = ArrayList<MatoPattern>(patternsJSON.length())
+ var mPatternJSON: JSONObject
+ for(i in 0 until patternsJSON.length()){
+ mPatternJSON = patternsJSON.getJSONObject(i)
+
+ val stopsJSON = mPatternJSON.getJSONArray("stops")
+
+ val stopsCodes = ArrayList<String>(stopsJSON.length())
+ for(k in 0 until stopsJSON.length()){
+ stopsCodes.add(
+ stopsJSON.getJSONObject(k).getString("gtfsId")
+ )
+ }
+
+ val geometry = mPatternJSON.getJSONObject("patternGeometry")
+ val numGeo = geometry.getInt("length")
+ val polyline = geometry.getString("points")
+
+ patternsOut.add(
+ MatoPattern(
+ mPatternJSON.getString("name"), mPatternJSON.getString("code"),
+ mPatternJSON.getString("semanticHash"), mPatternJSON.getInt("directionId"),
+ routeGtfsId,mPatternJSON.getString("headsign"), polyline, numGeo, stopsCodes
+ )
+ )
+ }
+ return patternsOut
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt b/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt
--- a/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt
+++ b/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt
@@ -31,7 +31,7 @@
listener: Response.Listener<List<Palina>>,
errorListener: Response.ErrorListener,
) : MapiVolleyRequest<List<Palina>>(
- MatoAPIFetcher.QueryType.ALL_STOPS,listener, errorListener) {
+ MatoQueries.QueryType.ALL_STOPS,listener, errorListener) {
private val FEEDS = JSONArray()
init {
@@ -73,7 +73,7 @@
return Response.success(palinas, HttpHeaderParser.parseCacheHeaders(response))
}
companion object{
- val FEEDS_STR = arrayOf("gtt")
+ //val FEEDS_STR = arrayOf("gtt")
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/DBUpdateWorker.java b/src/it/reyboz/bustorino/data/DBUpdateWorker.java
--- a/src/it/reyboz/bustorino/data/DBUpdateWorker.java
+++ b/src/it/reyboz/bustorino/data/DBUpdateWorker.java
@@ -47,13 +47,13 @@
public static final int SUCCESS_NO_ACTION_NEEDED = 9;
public static final int SUCCESS_UPDATE_DONE = 1;
- private final int notifi_ID=62341;
+ private final static int NOTIFIC_ID =32198;
public static final String FORCED_UPDATE = "FORCED-UPDATE";
public static final String DEBUG_TAG = "Busto-UpdateWorker";
- private static final long UPDATE_MIN_DELAY= 3*7*24*3600; //3 weeks
+ private static final long UPDATE_MIN_DELAY= 9*24*3600; //9 days
public DBUpdateWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
@@ -66,7 +66,14 @@
public Result doWork() {
//register Notification channel
final Context con = getApplicationContext();
- Notifications.createDefaultNotificationChannel(con);
+ //Notifications.createDefaultNotificationChannel(con);
+ //Use the new notification channels
+ Notifications.createNotificationChannel(con,con.getString(R.string.database_notification_channel),
+ con.getString(R.string.database_notification_channel_desc), NotificationManagerCompat.IMPORTANCE_LOW,
+ Notifications.DB_UPDATE_CHANNELS_ID
+ );
+ final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
+ final int notification_ID = 32198;
final SharedPreferences shPr = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences),MODE_PRIVATE);
final int current_DB_version = shPr.getInt(DatabaseUpdate.DB_VERSION_KEY,-10);
@@ -77,7 +84,17 @@
final long lastDBUpdateTime = shPr.getLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, 0);
long currentTime = System.currentTimeMillis()/1000;
- final int notificationID = showNotification();
+ //showNotification(notificationManager, notification_ID);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(con,
+ Notifications.DB_UPDATE_CHANNELS_ID)
+ .setContentTitle(con.getString(R.string.database_update_msg_notif))
+ .setProgress(0,0,true)
+ .setPriority(NotificationCompat.PRIORITY_LOW);
+ builder.setSmallIcon(R.drawable.ic_bus_orange);
+
+
+ notificationManager.notify(notification_ID,builder.build());
+
Log.d(DEBUG_TAG, "Have previous version: "+current_DB_version +" and new version "+new_DB_version);
Log.d(DEBUG_TAG, "Update compulsory: "+isUpdateCompulsory);
/*
@@ -95,7 +112,7 @@
if (!(current_DB_version < new_DB_version || currentTime > lastDBUpdateTime + UPDATE_MIN_DELAY )
&& !isUpdateCompulsory) {
//don't need to update
- cancelNotification(notificationID);
+ cancelNotification(notification_ID);
return ListenableWorker.Result.success(new Data.Builder().
putInt(SUCCESS_REASON_KEY, SUCCESS_NO_ACTION_NEEDED).build());
}
@@ -106,8 +123,7 @@
DatabaseUpdate.setDBUpdatingFlag(con, shPr,false);
if (resultUpdate != DatabaseUpdate.Result.DONE){
- Fetcher.Result result = resultAtomicReference.get();
-
+ //Fetcher.Result result = resultAtomicReference.get();
final Data.Builder dataBuilder = new Data.Builder();
switch (resultUpdate){
case ERROR_STOPS_DOWNLOAD:
@@ -117,7 +133,7 @@
dataBuilder.put(ERROR_REASON_KEY, ERROR_DOWNLOADING_LINES);
break;
}
- cancelNotification(notificationID);
+ cancelNotification(notification_ID);
return ListenableWorker.Result.failure(dataBuilder.build());
}
Log.d(DEBUG_TAG, "Update finished successfully!");
@@ -127,7 +143,7 @@
currentTime = System.currentTimeMillis()/1000;
editor.putLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, currentTime);
editor.apply();
- cancelNotification(notificationID);
+ cancelNotification(notification_ID);
return ListenableWorker.Result.success(new Data.Builder().putInt(SUCCESS_REASON_KEY, SUCCESS_UPDATE_DONE).build());
}
@@ -144,19 +160,21 @@
.build();
}
-
- private int showNotification(){
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), Notifications.DEFAULT_CHANNEL_ID)
+ /*
+ private int showNotification(@NonNull final NotificationManagerCompat notificManager, final int notification_ID,
+ final String channel_ID){
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channel_ID)
.setContentTitle("Libre BusTO - Updating Database")
.setProgress(0,0,true)
.setPriority(NotificationCompat.PRIORITY_LOW);
builder.setSmallIcon(R.drawable.ic_bus_orange);
- final NotificationManagerCompat notifcManager = NotificationManagerCompat.from(getApplicationContext());
- final int notification_ID = 32198;
- notifcManager.notify(notification_ID,builder.build());
+
+
+ notificManager.notify(notification_ID,builder.build());
return notification_ID;
}
+ */
private void cancelNotification(int notificationID){
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
diff --git a/src/it/reyboz/bustorino/data/DatabaseUpdate.java b/src/it/reyboz/bustorino/data/DatabaseUpdate.java
--- a/src/it/reyboz/bustorino/data/DatabaseUpdate.java
+++ b/src/it/reyboz/bustorino/data/DatabaseUpdate.java
@@ -22,22 +22,30 @@
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
-import androidx.core.content.ContextCompat;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.Observer;
import androidx.work.*;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Route;
-import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.mato.MatoAPIFetcher;
+import it.reyboz.bustorino.data.gtfs.GtfsAgency;
+import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
+import it.reyboz.bustorino.data.gtfs.GtfsDBDao;
+import it.reyboz.bustorino.data.gtfs.GtfsFeed;
+import it.reyboz.bustorino.data.gtfs.GtfsRoute;
+import it.reyboz.bustorino.data.gtfs.MatoPattern;
+import it.reyboz.bustorino.data.gtfs.PatternStop;
+import kotlin.Pair;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -59,130 +67,188 @@
DONE, ERROR_STOPS_DOWNLOAD, ERROR_LINES_DOWNLOAD
}
- /**
- * Request the server the version of the database
- * @return the version of the DB, or an error code
- */
- public static int getNewVersion(){
- AtomicReference<Fetcher.Result> gres = new AtomicReference<>();
- String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
- if(networkRequest == null){
- return VERSION_UNAVAILABLE;
- }
+ /**
+ * Request the server the version of the database
+ * @return the version of the DB, or an error code
+ */
+ public static int getNewVersion(){
+ AtomicReference<Fetcher.Result> gres = new AtomicReference<>();
+ String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
+ if(networkRequest == null){
+ return VERSION_UNAVAILABLE;
+ }
- try {
- JSONObject resp = new JSONObject(networkRequest);
- return resp.getInt("id");
- } catch (JSONException e) {
- e.printStackTrace();
- Log.e(DEBUG_TAG,"Error: wrong JSON response\nResponse:\t"+networkRequest);
- return JSON_PARSING_ERROR;
- }
+ try {
+ JSONObject resp = new JSONObject(networkRequest);
+ return resp.getInt("id");
+ } catch (JSONException e) {
+ e.printStackTrace();
+ Log.e(DEBUG_TAG,"Error: wrong JSON response\nResponse:\t"+networkRequest);
+ return JSON_PARSING_ERROR;
}
- /**
- * Run the DB Update
- * @param con a context
- * @param gres a result reference
- * @return result of the update
- */
- public static Result performDBUpdate(Context con, AtomicReference<Fetcher.Result> gres) {
+ }
+ private static boolean updateGTFSAgencies(Context con, AtomicReference<Fetcher.Result> res){
- final FiveTAPIFetcher f = new FiveTAPIFetcher();
- /*
- final ArrayList<Stop> stops = f.getAllStopsFromGTT(gres);
- //final ArrayList<ContentProviderOperation> cpOp = new ArrayList<>();
+ final GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
- if (gres.get() != Fetcher.Result.OK) {
- Log.w(DEBUG_TAG, "Something went wrong downloading");
- return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
+ final Pair<List<GtfsFeed>, ArrayList<GtfsAgency>> respair = MatoAPIFetcher.Companion.getFeedsAndAgencies(
+ con, res
+ );
- }
+ dao.insertAgenciesWithFeeds(respair.getFirst(), respair.getSecond());
- */
- final NextGenDB dbHelp = new NextGenDB(con.getApplicationContext());
- final SQLiteDatabase db = dbHelp.getWritableDatabase();
+ return true;
+ }
+ private static boolean updateGTFSRoutes(Context con, AtomicReference<Fetcher.Result> res){
- final List<Palina> palinasMatoAPI = MatoAPIFetcher.Companion.getAllStopsGTT(con, gres);
- if (gres.get() != Fetcher.Result.OK) {
- Log.w(DEBUG_TAG, "Something went wrong downloading");
- return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
+ final GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
- }
- //TODO: Get the type of stop from the lines
- //Empty the needed tables
- db.beginTransaction();
- //db.execSQL("DELETE FROM "+StopsTable.TABLE_NAME);
- //db.delete(LinesTable.TABLE_NAME,null,null);
-
- //put new data
- long startTime = System.currentTimeMillis();
-
- Log.d(DEBUG_TAG, "Inserting " + palinasMatoAPI.size() + " stops");
- for (final Palina p : palinasMatoAPI) {
- final ContentValues cv = new ContentValues();
-
- cv.put(NextGenDB.Contract.StopsTable.COL_ID, p.ID);
- cv.put(NextGenDB.Contract.StopsTable.COL_NAME, p.getStopDefaultName());
- if (p.location != null)
- cv.put(NextGenDB.Contract.StopsTable.COL_LOCATION, p.location);
- cv.put(NextGenDB.Contract.StopsTable.COL_LAT, p.getLatitude());
- cv.put(NextGenDB.Contract.StopsTable.COL_LONG, p.getLongitude());
- if (p.getAbsurdGTTPlaceName() != null) cv.put(NextGenDB.Contract.StopsTable.COL_PLACE, p.getAbsurdGTTPlaceName());
- cv.put(NextGenDB.Contract.StopsTable.COL_LINES_STOPPING, p.routesThatStopHereToString());
- if (p.type != null) cv.put(NextGenDB.Contract.StopsTable.COL_TYPE, p.type.getCode());
- if (p.gtfsID != null) cv.put(NextGenDB.Contract.StopsTable.COL_GTFS_ID, p.gtfsID);
- //Log.d(DEBUG_TAG,cv.toString());
- //cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
- //valuesArr[i] = cv;
- db.replace(NextGenDB.Contract.StopsTable.TABLE_NAME, null, cv);
+ final List<GtfsRoute> routes= MatoAPIFetcher.Companion.getRoutes(
+ con, res
+ );
+ dao.insertRoutes(routes);
+ if(res.get()!= Fetcher.Result.OK){
+ return false;
+ }
+ final ArrayList<String> gtfsRoutesIDs = new ArrayList<>(routes.size());
+ for(GtfsRoute r: routes){
+ gtfsRoutesIDs.add(r.getGtfsId());
+ }
+ long t0 = System.currentTimeMillis();
+ final ArrayList<MatoPattern> patterns = MatoAPIFetcher.Companion.getPatternsWithStops(con,gtfsRoutesIDs,res);
+ long tend = System.currentTimeMillis() - t0;
+ Log.d(DEBUG_TAG, "Downloaded patterns in "+tend+" ms");
+ if(res.get()!=Fetcher.Result.OK){
+ Log.e(DEBUG_TAG, "Something went wrong downloading patterns");
+ return false;
+ }
+ final ArrayList<PatternStop> patternStops = new ArrayList<>(patterns.size());
+ for(MatoPattern p: patterns){
+ final ArrayList<String> stopsIDs = p.getStopsGtfsIDs();
+ for (int i=0; i<stopsIDs.size(); i++){
+ patternStops.add(new PatternStop(p.getCode(),stopsIDs.get(i), i));
}
- db.setTransactionSuccessful();
- db.endTransaction();
- long endTime = System.currentTimeMillis();
- Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s");
+ }
+ dao.insertPatterns(patterns);
+ dao.insertPatternStops(patternStops);
- final ArrayList<Route> routes = f.getAllLinesFromGTT(gres);
+ return true;
+ }
- if (routes == null) {
- Log.w(DEBUG_TAG, "Something went wrong downloading the lines");
- dbHelp.close();
- return DatabaseUpdate.Result.ERROR_LINES_DOWNLOAD;
- }
+ /**
+ * Run the DB Update
+ * @param con a context
+ * @param gres a result reference
+ * @return result of the update
+ */
+ public static Result performDBUpdate(Context con, AtomicReference<Fetcher.Result> gres) {
- db.beginTransaction();
- startTime = System.currentTimeMillis();
- for (Route r : routes) {
- final ContentValues cv = new ContentValues();
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_NAME, r.getName());
- switch (r.type) {
- case BUS:
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "URBANO");
- break;
- case RAILWAY:
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "FERROVIA");
- break;
- case LONG_DISTANCE_BUS:
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "EXTRA");
- break;
- }
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_DESCRIPTION, r.description);
-
- //db.insert(LinesTable.TABLE_NAME,null,cv);
- int rows = db.update(NextGenDB.Contract.LinesTable.TABLE_NAME, cv, NextGenDB.Contract.LinesTable.COLUMN_NAME + " = ?", new String[]{r.getName()});
- if (rows < 1) { //we haven't changed anything
- db.insert(NextGenDB.Contract.LinesTable.TABLE_NAME, null, cv);
- }
- }
- db.setTransactionSuccessful();
- db.endTransaction();
- endTime = System.currentTimeMillis();
- Log.d(DEBUG_TAG, "Inserting lines took: " + ((double) (endTime - startTime) / 1000) + " s");
+ final FiveTAPIFetcher f = new FiveTAPIFetcher();
+
+ final NextGenDB dbHelp = new NextGenDB(con.getApplicationContext());
+ final SQLiteDatabase db = dbHelp.getWritableDatabase();
+
+ final List<Palina> palinasMatoAPI = MatoAPIFetcher.Companion.getAllStopsGTT(con, gres);
+ if (gres.get() != Fetcher.Result.OK) {
+ Log.w(DEBUG_TAG, "Something went wrong downloading");
+ return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
+
+ }
+ //TODO: Get the type of stop from the lines
+ //Empty the needed tables
+ db.beginTransaction();
+ //db.execSQL("DELETE FROM "+StopsTable.TABLE_NAME);
+ //db.delete(LinesTable.TABLE_NAME,null,null);
+
+ //put new data
+ long startTime = System.currentTimeMillis();
+
+ Log.d(DEBUG_TAG, "Inserting " + palinasMatoAPI.size() + " stops");
+ for (final Palina p : palinasMatoAPI) {
+ final ContentValues cv = new ContentValues();
+
+ cv.put(NextGenDB.Contract.StopsTable.COL_ID, p.ID);
+ cv.put(NextGenDB.Contract.StopsTable.COL_NAME, p.getStopDefaultName());
+ if (p.location != null)
+ cv.put(NextGenDB.Contract.StopsTable.COL_LOCATION, p.location);
+ cv.put(NextGenDB.Contract.StopsTable.COL_LAT, p.getLatitude());
+ cv.put(NextGenDB.Contract.StopsTable.COL_LONG, p.getLongitude());
+ if (p.getAbsurdGTTPlaceName() != null) cv.put(NextGenDB.Contract.StopsTable.COL_PLACE, p.getAbsurdGTTPlaceName());
+ cv.put(NextGenDB.Contract.StopsTable.COL_LINES_STOPPING, p.routesThatStopHereToString());
+ if (p.type != null) cv.put(NextGenDB.Contract.StopsTable.COL_TYPE, p.type.getCode());
+ if (p.gtfsID != null) cv.put(NextGenDB.Contract.StopsTable.COL_GTFS_ID, p.gtfsID);
+ //Log.d(DEBUG_TAG,cv.toString());
+ //cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
+ //valuesArr[i] = cv;
+ db.replace(NextGenDB.Contract.StopsTable.TABLE_NAME, null, cv);
+
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ long endTime = System.currentTimeMillis();
+ Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s");
+
+ // GTFS data fetching
+ AtomicReference<Fetcher.Result> gtfsRes = new AtomicReference<>(Fetcher.Result.OK);
+ updateGTFSAgencies(con, gtfsRes);
+ if (gtfsRes.get()!= Fetcher.Result.OK){
+ Log.w(DEBUG_TAG, "Could not insert the feeds and agencies stuff");
+ } else{
+ Log.d(DEBUG_TAG, "Done downloading agencies");
+ }
+ gtfsRes.set(Fetcher.Result.OK);
+ updateGTFSRoutes(con,gtfsRes);
+ if (gtfsRes.get()!= Fetcher.Result.OK){
+ Log.w(DEBUG_TAG, "Could not insert the routes into DB");
+ } else{
+ Log.d(DEBUG_TAG, "Done downloading routes from MaTO");
+ }
+ /*
+ final ArrayList<Route> routes = f.getAllLinesFromGTT(gres);
+
+ if (routes == null) {
+ Log.w(DEBUG_TAG, "Something went wrong downloading the lines");
dbHelp.close();
+ return DatabaseUpdate.Result.ERROR_LINES_DOWNLOAD;
+
+ }
+
+ db.beginTransaction();
+ startTime = System.currentTimeMillis();
+ for (Route r : routes) {
+ final ContentValues cv = new ContentValues();
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_NAME, r.getName());
+ switch (r.type) {
+ case BUS:
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "URBANO");
+ break;
+ case RAILWAY:
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "FERROVIA");
+ break;
+ case LONG_DISTANCE_BUS:
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "EXTRA");
+ break;
+ }
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_DESCRIPTION, r.description);
- return DatabaseUpdate.Result.DONE;
+ //db.insert(LinesTable.TABLE_NAME,null,cv);
+ int rows = db.update(NextGenDB.Contract.LinesTable.TABLE_NAME, cv, NextGenDB.Contract.LinesTable.COLUMN_NAME + " = ?", new String[]{r.getName()});
+ if (rows < 1) { //we haven't changed anything
+ db.insert(NextGenDB.Contract.LinesTable.TABLE_NAME, null, cv);
+ }
}
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ endTime = System.currentTimeMillis();
+ Log.d(DEBUG_TAG, "Inserting lines took: " + ((double) (endTime - startTime) / 1000) + " s");
+
+ */
+ dbHelp.close();
+
+ return DatabaseUpdate.Result.DONE;
+ }
public static boolean setDBUpdatingFlag(Context con, boolean value){
final SharedPreferences shPr = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences),MODE_PRIVATE);
@@ -203,7 +269,7 @@
final SharedPreferences theShPr = PreferencesHolder.getMainSharedPreferences(con);
final WorkManager workManager = WorkManager.getInstance(con);
PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 7, TimeUnit.DAYS)
- .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build();
@@ -221,4 +287,12 @@
TODO
}
*/
+
+ public static void watchUpdateWorkStatus(Context context, @NonNull LifecycleOwner lifecycleOwner,
+ @NonNull Observer<? super List<WorkInfo>> observer) {
+ WorkManager workManager = WorkManager.getInstance(context);
+ workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG).observe(
+ lifecycleOwner, observer
+ );
+ }
}
diff --git a/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
--- a/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
@@ -19,13 +19,12 @@
import android.content.Context
import android.util.Log
-import java.util.ArrayList
class CsvTableInserter(
val tableName: String, context: Context
) {
private val database: GtfsDatabase = GtfsDatabase.getGtfsDatabase(context)
- private val dao: StaticGtfsDao = database.gtfsDao()
+ private val databaseDao: GtfsDBDao = database.gtfsDao()
private val elementsList: MutableList< in GtfsTable> = mutableListOf()
@@ -35,12 +34,12 @@
private var countInsert = 0
init {
if(tableName == "stop_times") {
- stopsIDsPresent = dao.getAllStopsIDs().toHashSet()
- tripsIDsPresent = dao.getAllTripsIDs().toHashSet()
+ stopsIDsPresent = databaseDao.getAllStopsIDs().toHashSet()
+ tripsIDsPresent = databaseDao.getAllTripsIDs().toHashSet()
Log.d(DEBUG_TAG, "num stop IDs present: "+ stopsIDsPresent!!.size)
Log.d(DEBUG_TAG, "num trips IDs present: "+ tripsIDsPresent!!.size)
} else if(tableName == "routes"){
- dao.deleteAllRoutes()
+ databaseDao.deleteAllRoutes()
}
}
@@ -77,7 +76,7 @@
//have to insert
if (tableName == "routes")
- dao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
+ databaseDao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
else
insertDataInDatabase()
@@ -90,21 +89,21 @@
countInsert += elementsList.size
when(tableName){
"stops" -> {
- dao.insertStops(elementsList.filterIsInstance<GtfsStop>())
+ databaseDao.insertStops(elementsList.filterIsInstance<GtfsStop>())
}
- "routes" -> dao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
- "calendar" -> dao.insertServices(elementsList.filterIsInstance<GtfsService>())
- "calendar_dates" -> dao.insertDates(elementsList.filterIsInstance<GtfsServiceDate>())
- "trips" -> dao.insertTrips(elementsList.filterIsInstance<GtfsTrip>())
- "stop_times"-> dao.insertStopTimes(elementsList.filterIsInstance<GtfsStopTime>())
- "shapes" -> dao.insertShapes(elementsList.filterIsInstance<GtfsShape>())
+ "routes" -> databaseDao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
+ "calendar" -> databaseDao.insertServices(elementsList.filterIsInstance<GtfsService>())
+ "calendar_dates" -> databaseDao.insertDates(elementsList.filterIsInstance<GtfsServiceDate>())
+ "trips" -> databaseDao.insertTrips(elementsList.filterIsInstance<GtfsTrip>())
+ "stop_times"-> databaseDao.insertStopTimes(elementsList.filterIsInstance<GtfsStopTime>())
+ "shapes" -> databaseDao.insertShapes(elementsList.filterIsInstance<GtfsShape>())
}
///if(elementsList.size < MAX_ELEMENTS)
}
fun finishInsert(){
insertDataInDatabase()
- Log.d(DEBUG_TAG, "Inserted "+countInsert+" elements from "+tableName);
+ Log.d(DEBUG_TAG, "Inserted $countInsert elements from $tableName")
}
companion object{
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt
@@ -0,0 +1,55 @@
+package it.reyboz.bustorino.data.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = GtfsAgency.TABLE_NAME)
+data class GtfsAgency(
+ @PrimaryKey
+ @ColumnInfo(name = COL_GTFS_ID)
+ val gtfsId: String,
+ @ColumnInfo(name = COL_NAME)
+ val name: String,
+ @ColumnInfo(name = COL_URL)
+ val url: String,
+ @ColumnInfo(name = COL_FAREURL)
+ val fareUrl: String?,
+ @ColumnInfo(name = COL_PHONE)
+ val phone: String?,
+ @Embedded var feed: GtfsFeed?
+): GtfsTable{
+ constructor(valuesByColumn: Map<String,String>) : this(
+ valuesByColumn[COL_GTFS_ID]!!,
+ valuesByColumn[COL_NAME]!!,
+ valuesByColumn[COL_URL]!!,
+ valuesByColumn[COL_FAREURL],
+ valuesByColumn[COL_PHONE],
+ null
+ )
+
+ companion object{
+ const val TABLE_NAME="gtfs_agencies"
+
+ const val COL_GTFS_ID="gtfs_id"
+ const val COL_NAME="ag_name"
+ const val COL_URL="ag_url"
+ const val COL_FAREURL = "fare_url"
+ const val COL_PHONE = "phone"
+
+ val COLUMNS = arrayOf(
+ COL_GTFS_ID,
+ COL_NAME,
+ COL_URL,
+ COL_FAREURL,
+ COL_PHONE
+ )
+ const val CREATE_SQL =
+ "CREATE TABLE $TABLE_NAME ( $COL_GTFS_ID )"
+ }
+
+ override fun getColumns(): Array<String> {
+ return COLUMNS
+ }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
rename from src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
rename to src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
--- a/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
@@ -21,8 +21,8 @@
import androidx.room.*
@Dao
-interface StaticGtfsDao {
- @Query("SELECT * FROM "+GtfsRoute.DB_TABLE+" ORDER BY "+GtfsRoute.COL_SORT_ORDER)
+interface GtfsDBDao {
+ @Query("SELECT * FROM "+GtfsRoute.DB_TABLE)
fun getAllRoutes() : LiveData<List<GtfsRoute>>
@Query("SELECT "+GtfsTrip.COL_TRIP_ID+" FROM "+GtfsTrip.DB_TABLE)
@@ -45,9 +45,9 @@
deleteAllRoutes()
insertRoutes(routes)
}
-
+ @Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insertRoutes(users: List<GtfsRoute>)
+ fun insertRoutes(routes: List<GtfsRoute>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertStops(stops: List<GtfsStop>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
@@ -87,4 +87,21 @@
@Query("DELETE FROM "+GtfsService.DB_TABLE)
fun deleteAllServices()
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertFeeds(feeds: List<GtfsFeed>)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertAgencies(agencies: List<GtfsAgency>)
+
+ @Transaction
+ fun insertAgenciesWithFeeds(feeds: List<GtfsFeed>, agencies: List<GtfsAgency>){
+ insertFeeds(feeds)
+ insertAgencies(agencies)
+ }
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertPatterns(patterns: List<MatoPattern>)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertPatternStops(patternStops: List<PatternStop>)
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
--- a/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
@@ -18,24 +18,30 @@
package it.reyboz.bustorino.data.gtfs
import android.content.Context
+import android.util.Log
import androidx.room.*
+import androidx.room.migration.Migration
@Database(
entities = [
+ GtfsFeed::class,
+ GtfsAgency::class,
GtfsServiceDate::class,
GtfsStop::class,
GtfsService::class,
GtfsRoute::class,
GtfsStopTime::class,
GtfsTrip::class,
- GtfsShape::class],
+ GtfsShape::class,
+ MatoPattern::class,
+ PatternStop::class
+ ],
version = GtfsDatabase.VERSION,
- exportSchema = false,
)
@TypeConverters(Converters::class)
-public abstract class GtfsDatabase : RoomDatabase() {
+abstract class GtfsDatabase : RoomDatabase() {
- abstract fun gtfsDao() : StaticGtfsDao
+ abstract fun gtfsDao() : GtfsDBDao
companion object{
@Volatile
@@ -44,14 +50,36 @@
fun getGtfsDatabase(context: Context): GtfsDatabase{
return INSTANCE ?: synchronized(this){
val instance = Room.databaseBuilder(context.applicationContext,
- GtfsDatabase::class.java,
- "gtfs_database").build()
+ GtfsDatabase::class.java,
+ "gtfs_database")
+ .addMigrations(MIGRATION_1_2)
+ .build()
INSTANCE = instance
instance
}
}
- const val VERSION = 1
+ const val VERSION = 2
const val FOREIGNKEY_ONDELETE = ForeignKey.CASCADE
+
+ val MIGRATION_1_2 = Migration(1,2) {
+ Log.d("BusTO-Database", "Upgrading from version 1 to version 2 the Room Database")
+ //create table for feeds
+ it.execSQL("CREATE TABLE IF NOT EXISTS `gtfs_feeds` (`feed_id` TEXT NOT NULL, PRIMARY KEY(`feed_id`))")
+ //create table gtfs_agencies
+ it.execSQL("CREATE TABLE IF NOT EXISTS `gtfs_agencies` (`gtfs_id` TEXT NOT NULL, `ag_name` TEXT NOT NULL, `ag_url` TEXT NOT NULL, `fare_url` TEXT, `phone` TEXT, `feed_id` TEXT, PRIMARY KEY(`gtfs_id`))")
+
+ //recreate routes
+ it.execSQL("DROP TABLE IF EXISTS `routes_table`")
+ it.execSQL("CREATE TABLE IF NOT EXISTS `routes_table` (`route_id` TEXT NOT NULL, `agency_id` TEXT NOT NULL, `route_short_name` TEXT NOT NULL, `route_long_name` TEXT NOT NULL, `route_desc` TEXT NOT NULL, `route_mode` TEXT NOT NULL, `route_color` TEXT NOT NULL, `route_text_color` TEXT NOT NULL, PRIMARY KEY(`route_id`))")
+
+ //create patterns and stops
+ it.execSQL("CREATE TABLE IF NOT EXISTS `mato_patterns` (`pattern_name` TEXT NOT NULL, `pattern_code` TEXT NOT NULL, `pattern_hash` TEXT NOT NULL, `pattern_direction_id` INTEGER NOT NULL, `pattern_route_id` TEXT NOT NULL, `pattern_headsign` TEXT, `pattern_polyline` TEXT NOT NULL, `pattern_polylength` INTEGER NOT NULL, PRIMARY KEY(`pattern_code`), FOREIGN KEY(`pattern_route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
+ it.execSQL("CREATE TABLE IF NOT EXISTS `patterns_stops` (`pattern_gtfs_id` TEXT NOT NULL, `stop_gtfs_id` TEXT NOT NULL, `stop_order` INTEGER NOT NULL, PRIMARY KEY(`pattern_gtfs_id`, `stop_gtfs_id`, `stop_order`), FOREIGN KEY(`pattern_gtfs_id`) REFERENCES `mato_patterns`(`pattern_code`) ON UPDATE NO ACTION ON DELETE CASCADE )")
+
+
+ }
+
+
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt
@@ -0,0 +1,50 @@
+/*
+ BusTO - Data components
+ Copyright (C) 2022 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.data.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = GtfsFeed.TABLE_NAME)
+data class GtfsFeed(
+ @PrimaryKey
+ @ColumnInfo(name = COL_GTFS_ID)
+ val gtfsId: String,
+): GtfsTable{
+ constructor(valuesByColumn: Map<String,String>) : this(
+ valuesByColumn[COL_GTFS_ID]!!,
+ )
+
+ companion object{
+ const val TABLE_NAME="gtfs_feeds"
+
+ const val COL_GTFS_ID="feed_id"
+
+
+ val COLUMNS = arrayOf(
+ COL_GTFS_ID,
+ )
+ const val CREATE_SQL =
+ "CREATE TABLE $TABLE_NAME ( $COL_GTFS_ID )"
+ }
+
+ override fun getColumns(): Array<String> {
+ return COLUMNS
+ }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt
@@ -0,0 +1,19 @@
+package it.reyboz.bustorino.data.gtfs
+
+enum class GtfsMode(val intType: Int) {
+ TRAM(0),
+ SUBWAY(1),
+ RAIL(2),
+ BUS(3),
+ FERRY(4),
+ CABLE_TRAM(5),
+ GONDOLA(6),
+ FUNICULAR(7),
+ TROLLEYBUS(11),
+ MONORAIL(12);
+
+ companion object {
+ private val VALUES = values()
+ fun getByValue(value: Int) = VALUES.firstOrNull { it.intType == value }
+ }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
--- a/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
@@ -23,26 +23,25 @@
@Entity(tableName=GtfsRoute.DB_TABLE)
data class GtfsRoute(
- @PrimaryKey @ColumnInfo(name = COL_ROUTE_ID)
- val ID: String,
- @ColumnInfo(name = "agency_id")
+ @PrimaryKey @ColumnInfo(name = COL_ROUTE_ID)
+ val gtfsId: String,
+ @ColumnInfo(name = "agency_id")
val agencyID: String,
- @ColumnInfo(name = "route_short_name")
+ @ColumnInfo(name = "route_short_name")
val shortName: String,
- @ColumnInfo(name = "route_long_name")
+ @ColumnInfo(name = "route_long_name")
val longName: String,
- @ColumnInfo(name = "route_desc")
+ @ColumnInfo(name = "route_desc")
val description: String,
- @ColumnInfo(name ="route_type")
- val type: String,
+ @ColumnInfo(name = COL_MODE)
+ val mode: GtfsMode,
//@ColumnInfo(name ="route_url")
//val url: String,
- @ColumnInfo(name ="route_color")
+ @ColumnInfo(name = COL_COLOR)
val color: String,
- @ColumnInfo(name ="route_text_color")
+ @ColumnInfo(name = COL_TEXT_COLOR)
val textColor: String,
- @ColumnInfo(name = COL_SORT_ORDER)
- val sortOrder: Int
+
): GtfsTable {
constructor(valuesByColumn: Map<String,String>) : this(
@@ -51,15 +50,17 @@
valuesByColumn["route_short_name"]!!,
valuesByColumn["route_long_name"]!!,
valuesByColumn["route_desc"]!!,
- valuesByColumn["route_type"]!!,
- valuesByColumn["route_color"]!!,
- valuesByColumn["route_text_color"]!!,
- valuesByColumn[COL_SORT_ORDER]?.toInt()!!
+ valuesByColumn["route_type"]?.toInt()?.let { GtfsMode.getByValue(it) }!!,
+ valuesByColumn[COL_COLOR]!!,
+ valuesByColumn[COL_TEXT_COLOR]!!,
)
companion object {
const val DB_TABLE: String="routes_table"
const val COL_SORT_ORDER: String="route_sort_order"
const val COL_ROUTE_ID = "route_id"
+ const val COL_MODE ="route_mode"
+ const val COL_COLOR="route_color"
+ const val COL_TEXT_COLOR="route_text_color"
val COLUMNS = arrayOf(COL_ROUTE_ID,
"agency_id",
@@ -71,6 +72,8 @@
"route_text_color",
COL_SORT_ORDER
)
+
+ //const val CREATE_SQL = ""
}
override fun getColumns(): Array<String> {
diff --git a/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt b/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt
@@ -0,0 +1,123 @@
+/*
+ BusTO - Data components
+ Copyright (C) 2022 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.data.gtfs
+
+import androidx.room.*
+import it.reyboz.bustorino.backend.Stop
+
+@Entity(tableName = MatoPattern.TABLE_NAME,
+ foreignKeys = [
+ ForeignKey(entity = GtfsRoute::class,
+ parentColumns = [GtfsRoute.COL_ROUTE_ID],
+ childColumns = [MatoPattern.COL_ROUTE_ID],
+ onDelete = ForeignKey.CASCADE,
+ )
+ ]
+)
+data class MatoPattern(
+ @ColumnInfo(name= COL_NAME)
+ val name: String,
+ @ColumnInfo(name= COL_CODE)
+ @PrimaryKey
+ val code: String,
+ @ColumnInfo(name= COL_SEMANTIC_HASH)
+ val semanticHash: String,
+ @ColumnInfo(name= COL_DIRECTION_ID)
+ val directionId: Int,
+ @ColumnInfo(name= COL_ROUTE_ID)
+ val routeGtfsId: String,
+ @ColumnInfo(name= COL_HEADSIGN)
+ var headsign: String?,
+ @ColumnInfo(name= COL_GEOMETRY_POLY)
+ val patternGeometryPoly: String,
+ @ColumnInfo(name= COL_GEOMETRY_LENGTH)
+ val patternGeometryLength: Int,
+ @Ignore
+ val stopsGtfsIDs: ArrayList<String>
+
+):GtfsTable{
+
+ @Ignore
+ val servingStops= ArrayList<Stop>(4)
+ constructor(
+ name: String, code:String,
+ semanticHash: String, directionId: Int,
+ routeGtfsId: String, headsign: String?,
+ patternGeometryPoly: String, patternGeometryLength: Int
+ ): this(name, code, semanticHash, directionId, routeGtfsId, headsign, patternGeometryPoly, patternGeometryLength, ArrayList<String>(4))
+
+ companion object{
+ const val TABLE_NAME="mato_patterns"
+
+ const val COL_NAME="pattern_name"
+ const val COL_CODE="pattern_code"
+ const val COL_ROUTE_ID="pattern_route_id"
+ const val COL_SEMANTIC_HASH="pattern_hash"
+ const val COL_DIRECTION_ID="pattern_direction_id"
+ const val COL_HEADSIGN="pattern_headsign"
+ const val COL_GEOMETRY_POLY="pattern_polyline"
+ const val COL_GEOMETRY_LENGTH="pattern_polylength"
+
+ val COLUMNS = arrayOf(
+ COL_NAME,
+ COL_CODE,
+ COL_ROUTE_ID,
+ COL_SEMANTIC_HASH,
+ COL_DIRECTION_ID,
+ COL_HEADSIGN,
+ COL_GEOMETRY_POLY,
+ COL_GEOMETRY_LENGTH
+ )
+ }
+ override fun getColumns(): Array<String> {
+ return COLUMNS
+ }
+}
+
+//DO NOT USE EMBEDDED!!! -> copies all data
+
+@Entity(tableName=PatternStop.TABLE_NAME,
+ primaryKeys = [
+ PatternStop.COL_PATTERN_ID,
+ PatternStop.COL_STOP_GTFS,
+ PatternStop.COL_ORDER
+ ],
+ foreignKeys = [
+ ForeignKey(entity = MatoPattern::class,
+ parentColumns = [MatoPattern.COL_CODE],
+ childColumns = [PatternStop.COL_PATTERN_ID],
+ onDelete = ForeignKey.CASCADE
+ )
+ ]
+)
+data class PatternStop(
+ @ColumnInfo(name= COL_PATTERN_ID)
+ val patternId: String,
+ @ColumnInfo(name=COL_STOP_GTFS)
+ val stopGtfsId: String,
+ @ColumnInfo(name=COL_ORDER)
+ val order: Int,
+){
+ companion object{
+ const val TABLE_NAME="patterns_stops"
+
+ const val COL_PATTERN_ID="pattern_gtfs_id"
+ const val COL_STOP_GTFS="stop_gtfs_id"
+ const val COL_ORDER="stop_order"
+ }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
--- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
@@ -28,6 +28,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
@@ -36,6 +37,8 @@
import androidx.appcompat.widget.AppCompatButton;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.work.WorkInfo;
+
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -49,6 +52,8 @@
import it.reyboz.bustorino.adapters.ArrivalsStopAdapter;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.backend.FiveTAPIFetcher.QueryType;
+import it.reyboz.bustorino.backend.mato.MapiArrivalRequest;
+import it.reyboz.bustorino.data.DatabaseUpdate;
import it.reyboz.bustorino.middleware.AppLocationManager;
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB.Contract.*;
@@ -78,7 +83,6 @@
private SquareStopAdapter dataAdapter;
private AutoFitGridLayoutManager gridLayoutManager;
- boolean canStartDBQuery = true;
private Location lastReceivedLocation = null;
private ProgressBar circlingProgressBar,flatProgressBar;
private int distance;
@@ -100,6 +104,10 @@
private ArrivalsManager arrivalsManager = null;
private ArrivalsStopAdapter arrivalsStopAdapter = null;
+ private boolean dbUpdateRunning = false;
+
+ private ArrayList<Stop> currentNearbyStops = new ArrayList<>();
+
public NearbyStopsFragment() {
// Required empty public constructor
}
@@ -129,10 +137,10 @@
}
locManager = AppLocationManager.getInstance(getContext());
fragmentLocationListener = new FragmentLocationListener(this);
- globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences),Context.MODE_PRIVATE);
-
-
- globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
+ if (getContext()!=null) {
+ globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE);
+ globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
+ }
}
@@ -153,27 +161,29 @@
titleTextView = root.findViewById(R.id.titleTextView);
switchButton = root.findViewById(R.id.switchButton);
- preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
+ scrollListener = new CommonScrollListener(mListener,false);
+ switchButton.setOnClickListener(v -> switchFragmentType());
+ Log.d(DEBUG_TAG, "onCreateView");
+
+ DatabaseUpdate.watchUpdateWorkStatus(getContext(), this, new Observer<List<WorkInfo>>() {
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- Log.d(DEBUG_TAG,"Key "+key+" was changed");
- if(key.equals(getString(R.string.databaseUpdatingPref))){
- if(!sharedPreferences.getBoolean(getString(R.string.databaseUpdatingPref),true)){
- canStartDBQuery = true;
- Log.d(DEBUG_TAG,"The database has finished updating, can start update now");
- }
+ public void onChanged(List<WorkInfo> workInfos) {
+ if(workInfos.isEmpty()) return;
+
+ WorkInfo wi = workInfos.get(0);
+ if (wi.getState() == WorkInfo.State.RUNNING && locManager.isRequesterRegistered(fragmentLocationListener)) {
+ locManager.removeLocationRequestFor(fragmentLocationListener);
+ dbUpdateRunning = true;
+ } else if(!locManager.isRequesterRegistered(fragmentLocationListener)){
+ locManager.addLocationRequestFor(fragmentLocationListener);
+ dbUpdateRunning = false;
}
}
- };
- scrollListener = new CommonScrollListener(mListener,false);
- switchButton.setOnClickListener(v -> {
- switchFragmentType();
});
- Log.d(DEBUG_TAG, "onCreateView");
return root;
}
- protected ArrayList<Stop> createStopListFromCursor(Cursor data){
+ static ArrayList<Stop> createStopListFromCursor(Cursor data){
ArrayList<Stop> stopList = new ArrayList<>();
final int col_id = data.getColumnIndex(StopsTable.COL_ID);
final int latInd = data.getColumnIndex(StopsTable.COL_LAT);
@@ -220,7 +230,7 @@
@Override
- public void onAttach(Context context) {
+ public void onAttach(@NonNull Context context) {
super.onAttach(context);
/// TODO: RISOLVERE PROBLEMA: il context qui e' l'Activity non il Fragment
if (context instanceof FragmentListenerMain) {
@@ -235,7 +245,6 @@
@Override
public void onPause() {
super.onPause();
- canStartDBQuery = false;
gridRecyclerView.setAdapter(null);
locManager.removeLocationRequestFor(fragmentLocationListener);
@@ -245,9 +254,9 @@
@Override
public void onResume() {
super.onResume();
- canStartDBQuery = !globalSharedPref.getBoolean(getString(R.string.databaseUpdatingPref),false);
try{
- if(canStartDBQuery) locManager.addLocationRequestFor(fragmentLocationListener);
+ if(!dbUpdateRunning && !locManager.isRequesterRegistered(fragmentLocationListener))
+ locManager.addLocationRequestFor(fragmentLocationListener);
} catch (SecurityException ex){
//ignored
//try another location provider
@@ -314,7 +323,8 @@
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
//BUILD URI
- lastReceivedLocation = args.getParcelable(BUNDLE_LOCATION);
+ if (args!=null)
+ lastReceivedLocation = args.getParcelable(BUNDLE_LOCATION);
Uri.Builder builder = new Uri.Builder();
builder.scheme("content").authority(AppDataProvider.AUTHORITY)
.appendPath("stops").appendPath("location")
@@ -331,50 +341,58 @@
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
if (0 > MAX_DISTANCE) throw new AssertionError();
//Cursor might be null
- if(cursor==null){
- Log.e(DEBUG_TAG,"Null cursor, something really wrong happened");
+ if (cursor == null) {
+ Log.e(DEBUG_TAG, "Null cursor, something really wrong happened");
return;
}
- Log.d(DEBUG_TAG, "Num stops found: "+cursor.getCount()+", Current distance: "+distance);
+ Log.d(DEBUG_TAG, "Num stops found: " + cursor.getCount() + ", Current distance: " + distance);
- if(!isDBUpdating() && (cursor.getCount()<MIN_NUM_STOPS && distance<=MAX_DISTANCE)){
- distance = distance*2;
+ if (!dbUpdateRunning && (cursor.getCount() < MIN_NUM_STOPS && distance <= MAX_DISTANCE)) {
+ distance = distance * 2;
Bundle d = new Bundle();
- d.putParcelable(BUNDLE_LOCATION,lastReceivedLocation);
- getLoaderManager().restartLoader(LOADER_ID,d,this);
+ d.putParcelable(BUNDLE_LOCATION, lastReceivedLocation);
+ getLoaderManager().restartLoader(LOADER_ID, d, this);
//Log.d(DEBUG_TAG, "Doubling distance now!");
return;
}
- Log.d("LoadFromCursor","Number of nearby stops: "+cursor.getCount());
+ Log.d("LoadFromCursor", "Number of nearby stops: " + cursor.getCount());
////////
+ if(cursor.getCount()>0)
+ currentNearbyStops = createStopListFromCursor(cursor);
- if(cursor.getCount()>0) {
- ArrayList<Stop> stopList = createStopListFromCursor(cursor);
- double minDistance = Double.POSITIVE_INFINITY;
- for(Stop s: stopList){
- minDistance = Math.min(minDistance, s.getDistanceFromLocation(lastReceivedLocation));
- }
+ showCurrentStops();
+ }
+ /**
+ * Display the stops, or run new set of requests for arrivals
+ */
+ private void showCurrentStops(){
+ if (currentNearbyStops.isEmpty()) {
+ setNoStopsLayout();
+ return;
+ }
- //quick trial to hopefully always get the stops in the correct order
- Collections.sort(stopList,new StopSorterByDistance(lastReceivedLocation));
- switch (fragment_type){
- case TYPE_STOPS:
- showStopsInRecycler(stopList);
- break;
- case TYPE_ARRIVALS:
- arrivalsManager = new ArrivalsManager(stopList);
- flatProgressBar.setVisibility(View.VISIBLE);
- flatProgressBar.setProgress(0);
- flatProgressBar.setIndeterminate(false);
- //for the moment, be satisfied with only one location
- //AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener);
- break;
- default:
- }
+ double minDistance = Double.POSITIVE_INFINITY;
+ for(Stop s: currentNearbyStops){
+ minDistance = Math.min(minDistance, s.getDistanceFromLocation(lastReceivedLocation));
+ }
- } else {
- setNoStopsLayout();
+
+ //quick trial to hopefully always get the stops in the correct order
+ Collections.sort(currentNearbyStops,new StopSorterByDistance(lastReceivedLocation));
+ switch (fragment_type){
+ case TYPE_STOPS:
+ showStopsInRecycler(currentNearbyStops);
+ break;
+ case TYPE_ARRIVALS:
+ arrivalsManager = new ArrivalsManager(currentNearbyStops);
+ flatProgressBar.setVisibility(View.VISIBLE);
+ flatProgressBar.setProgress(0);
+ flatProgressBar.setIndeterminate(false);
+ //for the moment, be satisfied with only one location
+ //AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener);
+ break;
+ default:
}
}
@@ -411,15 +429,12 @@
gridRecyclerView.setAdapter(arrivalsStopAdapter);
}
fragmentLocationListener.lastUpdateTime = -1;
- locManager.removeLocationRequestFor(fragmentLocationListener);
- locManager.addLocationRequestFor(fragmentLocationListener);
+ //locManager.removeLocationRequestFor(fragmentLocationListener);
+ //locManager.addLocationRequestFor(fragmentLocationListener);
+ showCurrentStops();
}
//useful methods
- protected boolean isDBUpdating(){
- return globalSharedPref.getBoolean(getString(R.string.databaseUpdatingPref),false);
- }
-
/////// GUI METHODS ////////
private void showStopsInRecycler(List<Stop> stops){
@@ -454,7 +469,8 @@
if(p.queryAllRoutes().size() == 0) continue;
for(Route r: p.queryAllRoutes()){
//if there are no routes, should not do anything
- routesPairList.add(new Pair<>(p,r));
+ if (r.passaggi != null && !r.passaggi.isEmpty())
+ routesPairList.add(new Pair<>(p,r));
}
}
if (getContext()==null){
@@ -493,31 +509,30 @@
messageTextView.setVisibility(View.GONE);
}
- class ArrivalsManager implements FiveTAPIVolleyRequest.ResponseListener, Response.ErrorListener{
- final HashMap<String,Palina> mStops;
- final Map<String,List<Route>> routesToAdd = new HashMap<>();
+ class ArrivalsManager implements Response.Listener<Palina>, Response.ErrorListener{
+ final HashMap<String,Palina> palinasDone = new HashMap<>();
+ //final Map<String,List<Route>> routesToAdd = new HashMap<>();
final static String REQUEST_TAG = "NearbyArrivals";
- private final QueryType[] types = {QueryType.ARRIVALS,QueryType.DETAILS};
final NetworkVolleyManager volleyManager;
int activeRequestCount = 0,reqErrorCount = 0, reqSuccessCount=0;
ArrivalsManager(List<Stop> stops){
- mStops = new HashMap<>();
volleyManager = NetworkVolleyManager.getInstance(getContext());
int MAX_ARRIVAL_STOPS = 35;
+ Date currentDate = new Date();
+ int timeRange = 3600;
+ int departures = 10;
+ int numreq = 0;
for(Stop s: stops.subList(0,Math.min(stops.size(), MAX_ARRIVAL_STOPS))){
- mStops.put(s.ID,new Palina(s));
- for(QueryType t: types) {
- final FiveTAPIVolleyRequest req = FiveTAPIVolleyRequest.getNewRequest(t, s.ID, this, this);
- if (req != null) {
- req.setTag(REQUEST_TAG);
- volleyManager.addToRequestQueue(req);
- activeRequestCount++;
- }
- }
+
+ final MapiArrivalRequest req = new MapiArrivalRequest(s.ID, currentDate, timeRange, departures, this, this);
+ req.setTag(REQUEST_TAG);
+ volleyManager.addToRequestQueue(req);
+ activeRequestCount++;
+ numreq++;
}
- flatProgressBar.setMax(activeRequestCount);
+ flatProgressBar.setMax(numreq);
}
@@ -546,40 +561,19 @@
}
@Override
- public void onResponse(Palina result, QueryType type) {
+ public void onResponse(Palina result) {
//counter for requests
activeRequestCount--;
reqSuccessCount++;
-
-
- final Palina palinaInMap = mStops.get(result.ID);
+ //final Palina palinaInMap = palinasDone.get(result.ID);
//palina cannot be null here
//sorry for the brutal crash when it happens
- if(palinaInMap == null) throw new IllegalStateException("Cannot get the palina from the map");
- //necessary to split the Arrivals and Details cases
- switch (type){
- case ARRIVALS:
- palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
- final List<Route> possibleRoutes = routesToAdd.get(result.ID);
- if(possibleRoutes!=null) {
- palinaInMap.addInfoFromRoutes(possibleRoutes);
- routesToAdd.remove(result.ID);
- }
- break;
- case DETAILS:
- if(palinaInMap.queryAllRoutes().size()>0){
- //merge the branches
- palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
- } else {
- routesToAdd.put(result.ID,result.queryAllRoutes());
- }
- break;
- default:
- throw new IllegalArgumentException("Wrong QueryType in onResponse");
- }
-
+ //if(palinaInMap == null) throw new IllegalStateException("Cannot get the palina from the map");
+ //add the palina to the successful one
+ //TODO: Avoid redoing everything every time a new Result arrives
+ palinasDone.put(result.ID, result);
final ArrayList<Palina> outList = new ArrayList<>();
- for(Palina p: mStops.values()){
+ for(Palina p: palinasDone.values()){
final List<Route> routes = p.queryAllRoutes();
if(routes!=null && routes.size()>0) outList.add(p);
}
@@ -613,14 +607,14 @@
public void onLocationChanged(Location location) {
//set adapter
float accuracy = location.getAccuracy();
- if(accuracy<60 && canStartDBQuery) {
+ if(accuracy<60 && !dbUpdateRunning) {
distance = 20;
final Bundle msgBundle = new Bundle();
msgBundle.putParcelable(BUNDLE_LOCATION,location);
getLoaderManager().restartLoader(LOADER_ID,msgBundle,callbacks);
}
lastUpdateTime = System.currentTimeMillis();
- Log.d("BusTO:NearPositListen","can start loader "+ canStartDBQuery);
+ Log.d("BusTO:NearPositListen","can start loader "+ !dbUpdateRunning);
}
@Override

File Metadata

Mime Type
text/plain
Expires
Sun, May 31, 05:22 (40 m, 14 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1929743
Default Alt Text
D83.1780197730.diff (151 KB)

Event Timeline