diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java b/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java --- a/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/Notifications.java @@ -1,9 +1,11 @@ package it.reyboz.bustorino.backend; +import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.Build; +import androidx.core.app.NotificationCompat; import it.reyboz.bustorino.R; public class Notifications { @@ -44,4 +46,35 @@ notificationManager.createNotificationChannel(channel); } } + + + public static Notification makeMatoDownloadNotification(Context context,String title){ + return new NotificationCompat.Builder(context, Notifications.DB_UPDATE_CHANNELS_ID) + //.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), Constants.PENDING_INTENT_FLAG_IMMUTABLE)) + .setSmallIcon(R.drawable.bus) + .setOngoing(true) + .setAutoCancel(true) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_MIN) + .setContentTitle(context.getString(R.string.app_name)) + .setLocalOnly(true) + .setVisibility(NotificationCompat.VISIBILITY_SECRET) + .setContentText(title) + .build(); + } + public static Notification makeMatoDownloadNotification(Context context){ + return makeMatoDownloadNotification(context, "Downloading data from MaTO"); + } + + public static void createDBNotificationChannel(Context context){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + Notifications.DB_UPDATE_CHANNELS_ID, + context.getString(R.string.database_notification_channel), + NotificationManager.IMPORTANCE_MIN + ); + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } } diff --git a/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt b/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt --- a/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt +++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt @@ -408,22 +408,7 @@ 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 } diff --git a/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoQueries.kt b/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoQueries.kt --- a/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoQueries.kt +++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoQueries.kt @@ -118,7 +118,7 @@ } } """ - + //Query for the patterns, with the associated route const val ROUTES_WITH_PATTERNS=""" query RoutesWithPatterns(${'$'}routes: [String]) { routes(ids: ${'$'}routes) { diff --git a/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java b/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java --- a/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java +++ b/app/src/main/java/it/reyboz/bustorino/data/DatabaseUpdate.java @@ -31,7 +31,6 @@ 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.mato.MatoAPIFetcher; import it.reyboz.bustorino.data.gtfs.GtfsAgency; import it.reyboz.bustorino.data.gtfs.GtfsDatabase; @@ -126,7 +125,9 @@ } //match patterns with routes - final ArrayList<PatternStop> patternStops = new ArrayList<>(patterns.size()); + final ArrayList<PatternStop> patternStops = makeStopsForPatterns(patterns); + final List<String> allPatternsCodeInDB = dao.getPatternsCodes(); + final HashSet<String> patternsCodesToDelete = new HashSet<>(allPatternsCodeInDB); for(MatoPattern p: patterns){ //scan patterns @@ -135,25 +136,47 @@ if (mRoute == null) { Log.e(DEBUG_TAG, "Error in parsing the route: " + p.getRouteGtfsId() + " , cannot find the IDs in the map"); } - for (int i=0; i<stopsIDs.size(); i++){ - final String ID = stopsIDs.get(i); - patternStops.add(new PatternStop(p.getCode(),ID, i)); + for (final String sID : stopsIDs) { + //add stops to pattern stops // save routes stopping in the stop - - if (!routesStoppingInStop.containsKey(ID)){ - routesStoppingInStop.put(ID, new HashSet<>()); + if (!routesStoppingInStop.containsKey(sID)) { + routesStoppingInStop.put(sID, new HashSet<>()); } - Set<String> mset= routesStoppingInStop.get(ID); + Set<String> mset = routesStoppingInStop.get(sID); assert mset != null; mset.add(mRoute.getShortName()); } + //finally, remove from deletion list + patternsCodesToDelete.remove(p.getCode()); } + // final time for insert dao.insertPatterns(patterns); + // clear patterns that are unused + Log.d(DEBUG_TAG, "Have to remove "+patternsCodesToDelete.size()+ " patterns from the DB"); + dao.deletePatternsWithCodes(new ArrayList<>(patternsCodesToDelete)); dao.insertPatternStops(patternStops); return routesStoppingInStop; } + /** + * Make the list of stops that each pattern does, to be inserted into the DB + * @param patterns the MatoPattern + * @return a list of PatternStop + */ + public static ArrayList<PatternStop> makeStopsForPatterns(List<MatoPattern> patterns){ + 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++) { + //add stops to pattern stops + final String ID = stopsIDs.get(i); + patternStops.add(new PatternStop(p.getCode(), ID, i)); + } + } + return patternStops; + } + /** * Run the DB Update diff --git a/app/src/main/java/it/reyboz/bustorino/data/MatoPatternsDownloadWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/MatoPatternsDownloadWorker.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/data/MatoPatternsDownloadWorker.kt @@ -0,0 +1,101 @@ +/* + BusTO - Data components + Copyright (C) 2023 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 + +import android.app.NotificationManager +import android.content.Context +import android.util.Log +import androidx.work.* +import it.reyboz.bustorino.backend.Fetcher +import it.reyboz.bustorino.backend.Notifications +import it.reyboz.bustorino.backend.mato.MatoAPIFetcher +import java.util.concurrent.atomic.AtomicReference + +class MatoPatternsDownloadWorker(appContext: Context, workerParams: WorkerParameters) + : CoroutineWorker(appContext, workerParams) { + + + override suspend fun doWork(): Result { + val routesList = inputData.getStringArray(ROUTES_KEYS) + + if (routesList== null){ + Log.e(DEBUG_TAG,"routes list given is null") + return Result.failure() + } + + val res = AtomicReference<Fetcher.Result>(Fetcher.Result.OK) + + val gtfsRepository = GtfsRepository(applicationContext) + val patterns = MatoAPIFetcher.getPatternsWithStops(applicationContext, routesList.asList().toMutableList(), res) + if (res.get() != Fetcher.Result.OK) { + Log.e(DatabaseUpdate.DEBUG_TAG, "Something went wrong downloading patterns") + return Result.failure() + } + + gtfsRepository.gtfsDao.insertPatterns(patterns) + //Insert the PatternStops + gtfsRepository.gtfsDao.insertPatternStops(DatabaseUpdate.makeStopsForPatterns(patterns)) + return Result.success() + } + + + override suspend fun getForegroundInfo(): ForegroundInfo { + val notificationManager = + applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val context = applicationContext + Notifications.createDBNotificationChannel(context) + + return ForegroundInfo(NOTIFICATION_ID, Notifications.makeMatoDownloadNotification(context)) + } + + + companion object{ + const val ROUTES_KEYS = "routesToDownload" + const val DEBUG_TAG="BusTO:MatoPattrnDownWRK" + const val NOTIFICATION_ID=21983102 + + const val TAG_PATTERNS ="matoPatternsDownload" + + + + fun downloadPatternsForRoutes(routesIds: List<String>, context: Context): Boolean{ + if(routesIds.isEmpty()) return false; + + val workManager = WorkManager.getInstance(context); + val info = workManager.getWorkInfosForUniqueWork(TAG_PATTERNS).get() + + val runNewWork = if(info.isEmpty()) true + else info[0].state!= WorkInfo.State.RUNNING && info[0].state!= WorkInfo.State.ENQUEUED + val addDat = if(info.isEmpty()) + null else info[0].state + + Log.d(DEBUG_TAG, "Request to download and insert patterns for ${routesIds.size} routes, proceed: $runNewWork, workstate: $addDat") + if(runNewWork){ + val routeIdsArray = routesIds.toTypedArray() + val dataBuilder = Data.Builder().putStringArray(ROUTES_KEYS,routeIdsArray) + + val requ = OneTimeWorkRequest.Builder(MatoPatternsDownloadWorker::class.java) + .setInputData(dataBuilder.build()).setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .addTag(TAG_PATTERNS) + .build() + workManager.enqueueUniqueWork(TAG_PATTERNS, ExistingWorkPolicy.KEEP, requ) + } + return true + } + } +} diff --git a/app/src/main/java/it/reyboz/bustorino/data/MatoDownloadTripsWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt rename from app/src/main/java/it/reyboz/bustorino/data/MatoDownloadTripsWorker.kt rename to app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt --- a/app/src/main/java/it/reyboz/bustorino/data/MatoDownloadTripsWorker.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt @@ -17,32 +17,26 @@ */ package it.reyboz.bustorino.data -import android.app.NotificationChannel import android.app.NotificationManager -import android.app.PendingIntent import android.content.Context -import android.content.Intent -import android.os.Build import android.util.Log -import androidx.core.app.NotificationCompat -import androidx.lifecycle.viewModelScope -import androidx.work.CoroutineWorker -import androidx.work.ForegroundInfo -import androidx.work.Worker -import androidx.work.WorkerParameters -import com.android.volley.Response -import it.reyboz.bustorino.R +import androidx.work.* import it.reyboz.bustorino.backend.Notifications import it.reyboz.bustorino.data.gtfs.GtfsTrip -import kotlinx.coroutines.launch import java.util.concurrent.CountDownLatch -class MatoDownloadTripsWorker(appContext: Context, workerParams: WorkerParameters) +class MatoTripsDownloadWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) { + override suspend fun doWork(): Result { - //val imageUriInput = - // inputData.("IMAGE_URI") ?: return Result.failure() + return downloadGtfsTrips() + } + + /** + * Download GTFS Trips from Mato + */ + private fun downloadGtfsTrips():Result{ val tripsList = inputData.getStringArray(TRIPS_KEYS) if (tripsList== null){ @@ -80,7 +74,7 @@ } Log.i( DEBUG_TAG,"Result download, so far, trips: ${queriedMatoTrips.size}, failed: ${failedMatoTripsDownload.size}," + - " succeded: ${downloadedMatoTrips.size}") + " succeded: ${downloadedMatoTrips.size}") //check if we can insert the trips requestCountDown.countDown() } @@ -102,35 +96,40 @@ val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val context = applicationContext - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - Notifications.DB_UPDATE_CHANNELS_ID, - context.getString(R.string.database_notification_channel), - NotificationManager.IMPORTANCE_MIN - ) - notificationManager.createNotificationChannel(channel) - } + Notifications.createDBNotificationChannel(context) - val notification = NotificationCompat.Builder(context, Notifications.DB_UPDATE_CHANNELS_ID) - //.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), Constants.PENDING_INTENT_FLAG_IMMUTABLE)) - .setSmallIcon(R.drawable.bus) - .setOngoing(true) - .setAutoCancel(true) - .setOnlyAlertOnce(true) - .setPriority(NotificationCompat.PRIORITY_MIN) - .setContentTitle(context.getString(R.string.app_name)) - .setLocalOnly(true) - .setVisibility(NotificationCompat.VISIBILITY_SECRET) - .setContentText("Downloading data") - .build() - return ForegroundInfo(NOTIFICATION_ID, notification) + return ForegroundInfo(NOTIFICATION_ID, Notifications.makeMatoDownloadNotification(context)) } companion object{ const val TRIPS_KEYS = "tripsToDownload" - const val WORK_TAG = "tripsDownloaderAndInserter" const val DEBUG_TAG="BusTO:MatoTripDownWRK" - const val NOTIFICATION_ID=424242 + const val NOTIFICATION_ID=42424221 + + const val TAG_TRIPS ="gtfsTripsDownload" + + fun downloadTripsFromMato(trips: List<String>, context: Context, debugTag: String): Boolean{ + if (trips.isEmpty()) return false + val workManager = WorkManager.getInstance(context) + val info = workManager.getWorkInfosForUniqueWork(TAG_TRIPS).get() + + val runNewWork = if(info.isEmpty()) true + else info[0].state!= WorkInfo.State.RUNNING && info[0].state!= WorkInfo.State.ENQUEUED + val addDat = if(info.isEmpty()) + null else info[0].state + Log.d(debugTag, "Request to download and insert ${trips.size} trips, proceed: $runNewWork, workstate: $addDat") + if(runNewWork) { + val tripsArr = trips.toTypedArray() + val dataBuilder = Data.Builder().putStringArray(TRIPS_KEYS, tripsArr) + //build() + val requ = OneTimeWorkRequest.Builder(MatoTripsDownloadWorker::class.java) + .setInputData(dataBuilder.build()).setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .addTag(TAG_TRIPS) + .build() + workManager.enqueueUniqueWork(TAG_TRIPS, ExistingWorkPolicy.KEEP, requ) + } + return true + } } } diff --git a/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt b/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt --- a/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt @@ -48,6 +48,8 @@ @Query("SELECT * FROM ${GtfsRoute.DB_TABLE} WHERE ${GtfsRoute.COL_AGENCY_ID} LIKE :agencyID") fun getRoutesByAgency(agencyID:String) : LiveData<List<GtfsRoute>> + @Query("SELECT ${MatoPattern.COL_CODE} FROM ${MatoPattern.TABLE_NAME} ") + fun getPatternsCodes(): List<String> @Query("SELECT * FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeID") fun getPatternsLiveDataByRouteID(routeID: String): LiveData<List<MatoPattern>> @@ -65,6 +67,9 @@ @Query("SELECT * FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_CODE} IN (:patternGtfsIDs)") fun getPatternsWithStopsFromIDs(patternGtfsIDs: List<String>) : LiveData<List<MatoPatternWithStops>> + @Query("SELECT ${MatoPattern.COL_CODE} FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_CODE} IN (:codes)") + fun getPatternsCodesInTheDB(codes: List<String>): List<String> + @Transaction @Query("SELECT * FROM ${GtfsTrip.DB_TABLE} WHERE ${GtfsTrip.COL_TRIP_ID} IN (:tripsIds)") fun getTripsFromIDs(tripsIds: List<String>) : List<GtfsTrip> @@ -73,10 +78,14 @@ @Query("SELECT * FROM ${GtfsTrip.DB_TABLE} WHERE ${GtfsTrip.COL_TRIP_ID} IN (:trips)") fun getTripPatternStops(trips: List<String>): LiveData<List<TripAndPatternWithStops>> + + fun getRoutesForFeed(feed:String): LiveData<List<GtfsRoute>>{ val agencyID = "${feed}:%" return getRoutesByAgency(agencyID) } + + @Transaction fun clearAndInsertRoutes(routes: List<GtfsRoute>){ deleteAllRoutes() @@ -111,6 +120,9 @@ fun deleteAllStops() @Query("DELETE FROM "+GtfsTrip.DB_TABLE) fun deleteAllTrips() + + @Query("DELETE FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_CODE} IN (:codes)") + fun deletePatternsWithCodes(codes: List<String>): Int @Update(onConflict = OnConflictStrategy.REPLACE) fun updateShapes(shapes: List<GtfsShape>) : Int diff --git a/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsTrip.kt b/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsTrip.kt --- a/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsTrip.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsTrip.kt @@ -120,7 +120,7 @@ parentColumn = GtfsTrip.COL_PATTERN_ID, entityColumn = MatoPattern.COL_CODE ) - val pattern: MatoPattern, + val pattern: MatoPattern?, @Relation( parentColumn = MatoPattern.COL_CODE, entityColumn = PatternStop.COL_PATTERN_ID, diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java @@ -47,6 +47,7 @@ import it.reyboz.bustorino.backend.gtfs.GtfsPositionUpdate; import it.reyboz.bustorino.backend.gtfs.GtfsUtils; import it.reyboz.bustorino.backend.utils; +import it.reyboz.bustorino.data.gtfs.MatoPattern; import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops; import it.reyboz.bustorino.map.*; import org.osmdroid.api.IGeoPoint; @@ -346,7 +347,7 @@ //mapViewModel.testCascade(); mapViewModel.getTripsGtfsIDsToQuery().observe(this, dat -> { Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: "+dat); - mapViewModel.downloadandInsertTripsInDB(dat); + mapViewModel.downloadTripsFromMato(dat); }); } } @@ -637,11 +638,11 @@ if(tripWithPatternStops == null){ noPatternsTrips.add(tripID); } - marker.setInfoWindow(new BusInfoWindow(map, update, tripWithPatternStops != null ? tripWithPatternStops.getPattern() : null, new BusInfoWindow.onTouchUp() { - @Override - public void onActionUp() { + MatoPattern markerPattern = null; + if(tripWithPatternStops != null && tripWithPatternStops.getPattern()!=null) + markerPattern = tripWithPatternStops.getPattern(); + marker.setInfoWindow(new BusInfoWindow(map, update, markerPattern , () -> { - } })); updateBusMarker(marker, update, true); diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapViewModel.kt --- a/app/src/main/java/it/reyboz/bustorino/fragments/MapViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapViewModel.kt @@ -20,15 +20,11 @@ import android.app.Application import android.util.Log import androidx.lifecycle.* -import androidx.work.* import com.android.volley.Response -import com.google.transit.realtime.GtfsRealtime.VehiclePosition import it.reyboz.bustorino.backend.NetworkVolleyManager -import it.reyboz.bustorino.backend.Result import it.reyboz.bustorino.backend.gtfs.GtfsPositionUpdate import it.reyboz.bustorino.backend.gtfs.GtfsRtPositionsRequest import it.reyboz.bustorino.data.* -import it.reyboz.bustorino.data.gtfs.GtfsTrip import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -39,10 +35,7 @@ */ class MapViewModel(application: Application): AndroidViewModel(application) { private val gtfsRepo = GtfsRepository(application) - private val executor = Executors.newFixedThreadPool(2) - private val oldRepo= OldDataRepository(executor, NextGenDB.getInstance(application)) - private val matoRepository = MatoRepository(application) private val netVolleyManager = NetworkVolleyManager.getInstance(application) @@ -107,13 +100,23 @@ //add "gtt:" prefix because it's implicit in GTFS Realtime API return@map it.map { pos -> "gtt:"+pos.tripID } } - // trips that are in the DB - val gtfsTripsInDB = tripsIDsInUpdates.switchMap { + //this holds the trips that have been downloaded but for which we have no pattern + /*private val gtfsTripsInDBMissingPattern = tripsIDsInUpdates.map { tripsIDs -> + val tripsInDB = gtfsRepo.gtfsDao.getTripsFromIDs(tripsIDs) + val tripsPatternCodes = tripsInDB.map { tr -> tr.patternId } + val codesInDB = gtfsRepo.gtfsDao.getPatternsCodesInTheDB(tripsPatternCodes) + + tripsInDB.filter { !(codesInDB.contains(it.patternId)) } + }*/ + //private val patternsCodesInDB = tripsDBPatterns.map { gtfsRepo.gtfsDao.getPatternsCodesInTheDB(it) } + + // trips that are in the DB, together with the pattern. If the pattern is not present in the DB, it's null + val gtfsTripsPatternsInDB = tripsIDsInUpdates.switchMap { //Log.i(DEBUG_TI, "tripsIds in updates changed: ${it.size}") gtfsRepo.gtfsDao.getTripPatternStops(it) } //trip IDs to query, which are not present in the DB - val tripsGtfsIDsToQuery: LiveData<List<String>> = gtfsTripsInDB.map { tripswithPatterns -> + val tripsGtfsIDsToQuery: LiveData<List<String>> = gtfsTripsPatternsInDB.map { tripswithPatterns -> val tripNames=tripswithPatterns.map { twp-> twp.trip.tripID } Log.i(DEBUG_TI, "Have ${tripswithPatterns.size} trips in the DB") if (tripsIDsInUpdates.value!=null) @@ -124,14 +127,21 @@ } } - val updatesWithTripAndPatterns = gtfsTripsInDB.map { tripPatterns-> + val updatesWithTripAndPatterns = gtfsTripsPatternsInDB.map { tripPatterns-> Log.i(DEBUG_TI, "Mapping trips and patterns") val mdict = HashMap<String,Pair<GtfsPositionUpdate, TripAndPatternWithStops?>>() + //missing patterns + val routesToDownload = HashSet<String>() if(positionsLiveData.value!=null) for(update in positionsLiveData.value!!){ val trID = update.tripID var found = false for(trip in tripPatterns){ + if (trip.pattern == null){ + //pattern is null, which means we have to download + // the pattern data from MaTO + routesToDownload.add(trip.trip.routeID) + } if (trip.trip.tripID == "gtt:$trID"){ found = true //insert directly @@ -140,10 +150,17 @@ } } if (!found){ + //Log.d(DEBUG_TI, "Cannot find pattern ${tr}") //give the update anyway mdict[trID] = Pair(update,null) } } + //have to request download of missing Patterns + if (routesToDownload.size > 0){ + Log.d(DEBUG_TI, "Have ${routesToDownload.size} missing patterns from the DB: $routesToDownload") + downloadMissingPatterns(ArrayList(routesToDownload)) + } + return@map mdict } /* @@ -152,38 +169,17 @@ 1 -> wait until they are all queried to insert in the DB 2 -> after each request finishes, insert it into the DB - Keep in mind that trips do not change very often, so they might only need to be inserted once every two months - TODO: find a way to avoid trips getting scrubbed (check if they are really scrubbed) + Keep in mind that trips DO CHANGE often, and so do the Patterns */ + fun downloadTripsFromMato(trips: List<String>): Boolean{ + return MatoTripsDownloadWorker.downloadTripsFromMato(trips,getApplication(), DEBUG_TI) + } + fun downloadMissingPatterns(routeIds: List<String>): Boolean{ + return MatoPatternsDownloadWorker.downloadPatternsForRoutes(routeIds, getApplication()) + } init { - /* - //what happens when the trips to query with Mato are determined - tripsIDsQueried.addSource(tripsGtfsIDsToQuery) { tripList -> - // avoid infinite loop of querying to Mato, insert in DB and - // triggering another query update - - val tripsToQuery = - Log.d(DEBUG_TI, "Querying trips IDs to Mato: $tripsToQuery") - for (t in tripsToQuery){ - matoRepository.requestTripUpdate(t,matoTripReqErrorList, matoTripCallback) - } - tripsIDsQueried.value = tripsToQuery - } - tripsToInsert.addSource(tripsFromMato) { matoTrips -> - if (tripsIDsQueried.value == null) return@addSource - val tripsIdsToInsert = matoTrips.map { trip -> trip.tripID } - - //val setTripsToInsert = HashSet(tripsIdsToInsert) - val tripsRequested = HashSet(tripsIDsQueried.value!!) - val insertInDB = tripsRequested.containsAll(tripsIdsToInsert) - if(insertInDB){ - gtfsRepo.gtfsDao.insertTrips(matoTrips) - } - Log.d(DEBUG_TI, "MatoTrips: ${matoTrips.size}, total trips req: ${tripsRequested.size}, inserting: $insertInDB") - } - */ Log.d(DEBUG_TI, "MapViewModel created") Log.d(DEBUG_TI, "Observers of positionsLiveData ${positionsLiveData.hasActiveObservers()}") @@ -198,36 +194,12 @@ )) positionsLiveData.value = n } - private val queriedMatoTrips = HashSet<String>() - private val downloadedMatoTrips = ArrayList<GtfsTrip>() - private val failedMatoTripsDownload = HashSet<String>() /** * Start downloading missing GtfsTrips and Insert them in the DB */ - fun downloadandInsertTripsInDB(trips: List<String>): Boolean{ - if (trips.isEmpty()) return false - val workManager = WorkManager.getInstance(getApplication()) - val info = workManager.getWorkInfosForUniqueWork(MatoDownloadTripsWorker.WORK_TAG).get() - - val runNewWork = if(info.isEmpty()){ - true - } else info[0].state!=WorkInfo.State.RUNNING && info[0].state!=WorkInfo.State.ENQUEUED - val addDat = if(info.isEmpty()) - null else info[0].state - Log.d(DEBUG_TI, "Request to download and insert ${trips.size} trips, proceed: $runNewWork, workstate: $addDat") - if(runNewWork) { - val tripsArr = trips.toTypedArray() - val data = Data.Builder().putStringArray(MatoDownloadTripsWorker.TRIPS_KEYS, tripsArr).build() - val requ = OneTimeWorkRequest.Builder(MatoDownloadTripsWorker::class.java) - .setInputData(data).setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) - .addTag(MatoDownloadTripsWorker.WORK_TAG) - .build() - workManager.enqueueUniqueWork(MatoDownloadTripsWorker.WORK_TAG, ExistingWorkPolicy.KEEP, requ) - } - return true - } + companion object{ const val DEBUG_TI="BusTO-MapViewModel" diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/SettingsFragment.java @@ -31,13 +31,11 @@ import androidx.lifecycle.Observer; import androidx.preference.*; import androidx.work.OneTimeWorkRequest; -import androidx.work.OutOfQuotaPolicy; import androidx.work.WorkInfo; import androidx.work.WorkManager; import it.reyboz.bustorino.R; import it.reyboz.bustorino.data.DatabaseUpdate; import it.reyboz.bustorino.data.GtfsMaintenanceWorker; -import it.reyboz.bustorino.data.MatoDownloadTripsWorker; import org.jetbrains.annotations.NotNull; import java.lang.ref.WeakReference;