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 @@ -11,6 +11,7 @@ Inserisci il nome della fermata Verifica l\'accesso ad Internet! Sembra che nessuna fermata abbia questo nome + Nessun passaggio trovato alla fermata Errore di lettura del sito 5T/GTT (dannato sito!) Fermata: %1$s Linee: %1$s diff --git a/res/values/strings.xml b/res/values/strings.xml --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -16,6 +16,7 @@ %s (unknown destination) Verify your Internet connection! Seems that no bus stop have this name + No arrivals found for this stop Error parsing the 5T/GTT website (damn site!) Name too short, type more characters and retry diff --git a/src/it/reyboz/bustorino/adapters/AdapterListener.java b/src/it/reyboz/bustorino/adapters/AdapterListener.java --- a/src/it/reyboz/bustorino/adapters/AdapterListener.java +++ b/src/it/reyboz/bustorino/adapters/AdapterListener.java @@ -1,3 +1,20 @@ +/* + BusTO - Adapter components + Copyright (C) 2021 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 . + */ package it.reyboz.bustorino.adapters; import it.reyboz.bustorino.backend.Stop; diff --git a/src/it/reyboz/bustorino/adapters/PalinaAdapter.java b/src/it/reyboz/bustorino/adapters/PalinaAdapter.java --- a/src/it/reyboz/bustorino/adapters/PalinaAdapter.java +++ b/src/it/reyboz/bustorino/adapters/PalinaAdapter.java @@ -30,15 +30,15 @@ import android.widget.ArrayAdapter; import android.widget.TextView; - import java.util.List; +import java.util.List; import java.util.Locale; -import it.reyboz.bustorino.BuildConfig; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.Palina; import it.reyboz.bustorino.backend.Passaggio; import it.reyboz.bustorino.backend.Route; import it.reyboz.bustorino.backend.utils; +import it.reyboz.bustorino.util.RouteSorterByArrivalTime; /** * This once was a ListView Adapter for BusLine[]. @@ -60,7 +60,7 @@ private static final int tramIcon = R.drawable.tram; private final String KEY_CAPITALIZE; - private Capitalize capit = Capitalize.DO_NOTHING; + private Capitalize capit; //private static final int cityIcon = R.drawable.city; @@ -89,6 +89,25 @@ super(context, row_layout, p.queryAllRoutes()); li = LayoutInflater.from(context); sort(new RouteSorterByArrivalTime()); + /* + sort(new Comparator() { + + @Override + public int compare(Route route, Route t1) { + LinesNameSorter sorter = new LinesNameSorter(); + if(route.getNameForDisplay()!= null){ + if(t1.getNameForDisplay()!=null){ + return sorter.compare(route.getNameForDisplay(), t1.getNameForDisplay()); + } + else return -1; + } else if(t1.getNameForDisplay()!=null){ + return +1; + } + else return 0; + } + }); + + */ KEY_CAPITALIZE = context.getString(R.string.pref_arrival_times_capit); SharedPreferences defSharPref = PreferenceManager.getDefaultSharedPreferences(context); defSharPref.registerOnSharedPreferenceChangeListener(this); diff --git a/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java b/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java --- a/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java +++ b/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java @@ -1,3 +1,20 @@ +/* + BusTO - Adapter components + Copyright (C) 2021 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 . + */ package it.reyboz.bustorino.adapters; import android.util.Log; diff --git a/src/it/reyboz/bustorino/backend/Fetcher.java b/src/it/reyboz/bustorino/backend/Fetcher.java --- a/src/it/reyboz/bustorino/backend/Fetcher.java +++ b/src/it/reyboz/bustorino/backend/Fetcher.java @@ -28,10 +28,11 @@ * for 404 special constant (see @FiveTAPIFetcher) * PARSER_ERROR: the server replied something that can't be parsed, probably it's not the data we're looking for (e.g. "PHP: Fatal Error")
* EMPTY_RESULT_SET: the response is valid and indicates there are no stops\routes\"passaggi"\results for your query
+ * NOT_FOUND: response is valid, no parsing errors, but the desired stops/routes wasn't found * QUERY_TOO_SHORT: input more characters and retry. */ enum Result { OK, CLIENT_OFFLINE, SERVER_ERROR, SETUP_ERROR,PARSER_ERROR, EMPTY_RESULT_SET, QUERY_TOO_SHORT, SERVER_ERROR_404, - CONNECTION_ERROR + CONNECTION_ERROR, NOT_FOUND } } diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java --- a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java +++ b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java @@ -50,7 +50,7 @@ if(response==null) { if(res.get()== Result.SERVER_ERROR_404) { Log.w(DEBUG_NAME,"Got 404, either the server failed, or the stop was not found, or the address is wrong"); - res.set(Result.EMPTY_RESULT_SET); + //res.set(Result.S); } return p; } @@ -384,9 +384,8 @@ res.set(Result.PARSER_ERROR); return null; } - String response = networkTools.queryURL(u,res,param); - return response; + return networkTools.queryURL(u,res,param); } /** diff --git a/src/it/reyboz/bustorino/backend/FiveTScraperFetcher.java b/src/it/reyboz/bustorino/backend/FiveTScraperFetcher.java --- a/src/it/reyboz/bustorino/backend/FiveTScraperFetcher.java +++ b/src/it/reyboz/bustorino/backend/FiveTScraperFetcher.java @@ -83,7 +83,7 @@ String busStopID = grep("^(.+) ", span.html()); if (busStopID == null) { //Log.e("BusStop", "Empty busStopID from " + span.html()); - res.set(Result.EMPTY_RESULT_SET); + res.set(Result.NOT_FOUND); return p; } diff --git a/src/it/reyboz/bustorino/backend/GTTJSONFetcher.java b/src/it/reyboz/bustorino/backend/GTTJSONFetcher.java --- a/src/it/reyboz/bustorino/backend/GTTJSONFetcher.java +++ b/src/it/reyboz/bustorino/backend/GTTJSONFetcher.java @@ -76,7 +76,7 @@ json.getJSONObject(0).getString("Linea"); // if we can get this, then there's something useful in the array. } catch(JSONException e) { Log.w(DEBUG_TAG, "No existing lines"); - res.set(Result.EMPTY_RESULT_SET); + res.set(Result.NOT_FOUND); return p; } diff --git a/src/it/reyboz/bustorino/backend/Palina.java b/src/it/reyboz/bustorino/backend/Palina.java --- a/src/it/reyboz/bustorino/backend/Palina.java +++ b/src/it/reyboz/bustorino/backend/Palina.java @@ -26,8 +26,11 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import it.reyboz.bustorino.util.LinesNameSorter; + /** * Timetable for multiple routes.
*
@@ -91,83 +94,42 @@ * @return array index for this route */ public int addRoute(String routeID, String destinazione, Route.Type type) { - this.routes.add(new Route(routeID, destinazione, type, new ArrayList<>(6))); - routesModified = true; - return this.routes.size() - 1; // last inserted element and pray that direct access to ArrayList elements really is direct + return addRoute(new Route(routeID, destinazione, type, new ArrayList<>(6))); } public int addRoute(Route r){ this.routes.add(r); routesModified = true; - return this.routes.size()-1; + buildRoutesString(); + return this.routes.size()-1; // last inserted element and pray that direct access to ArrayList elements really is direct } + public void setRoutes(List routeList){ routes = new ArrayList<>(routeList); } + @Nullable + @Override + protected String buildRoutesString() { + // no routes => no string + if(routes == null || routes.size() == 0) { + return ""; + } + final StringBuilder sb = new StringBuilder(); + final LinesNameSorter nameSorter = new LinesNameSorter(); + Collections.sort(routes, (o1, o2) -> nameSorter.compare(o1.getName().trim(), o2.getName().trim())); + int i, lenMinusOne = routes.size() - 1; + + for (i = 0; i < lenMinusOne; i++) { + sb.append(routes.get(i).getName().trim()).append(", "); + } + // last one: + sb.append(routes.get(i).getName()); + + setRoutesThatStopHereString(sb.toString()); + return routesThatStopHereToString(); + } -// /** -// * Clears a route timetable (or creates an empty route) and returns its index -// * -// * @param routeID name -// * @param destinazione end of line\terminus -// * @return array index for this route -// */ -// public int updateRoute(String routeID, String destinazione) { -// int s = this.routes.size(); -// RouteInternal r; -// -// for(int i = 0; i < s; i++) { -// r = routes.get(i); -// if(r.name.compareTo(routeID) == 0 && r.destinazione.compareTo(destinazione) == 0) { -// // capire se รจ possibile che ci siano stessa linea e stessa destinazione su 2 righe diverse del sito e qui una sovrascrive l'altra (probabilmente no) -// r.updateFlag(); -// r.deletePassaggio(); -// return i; -// } -// } -// -// return this.addRoute(routeID, destinazione); -// } -// -// /** -// * Deletes routes marked as "not updated" (= disappeared from the GTT website\API\whatever). -// * Sets all remaining routes to "not updated" because that's how this contraption works. -// */ -// public void finishUpdatingRoutes() { -// RouteInternal r; -// -// for(Iterator itr = this.routes.iterator(); itr.hasNext(); ) { -// r = itr.next(); -// if(r.unupdateFlag()) { -// itr.remove(); -// } -// } -// } -// /** -// * Gets the current timetable for a route. Returns null if the route doesn't exist. -// * This is slower than queryRouteByIndex. -// * -// * @return timetable (passaggi) -// */ -// public List queryRoute(String routeID) { -// for(Route r : this.routes) { -// if(routeID.equals(r.name)) { -// return r.getPassaggi(); -// } -// } -// -// return null; -// } -// -// /** -// * Gets the current timetable for this route, from its index in the array. -// * -// * @return timetable (passaggi) -// */ -// public List queryRouteByIndex(int index) { -// return this.routes.get(index).getPassaggi(); -// } protected void checkPassaggi(){ Passaggio.Source mSource = null; for (Route r: routes){ @@ -222,6 +184,7 @@ public int addInfoFromRoutes(List additionalRoutes){ if(routes == null || routes.size()==0) { this.routes = new ArrayList<>(additionalRoutes); + buildRoutesString(); return routes.size(); } int count=0; @@ -280,6 +243,7 @@ //MERGE INFO if(r.mergeRouteWithAnother(selected)) count++; } + if (count> 0) buildRoutesString(); return count; } diff --git a/src/it/reyboz/bustorino/backend/Stop.java b/src/it/reyboz/bustorino/backend/Stop.java --- a/src/it/reyboz/bustorino/backend/Stop.java +++ b/src/it/reyboz/bustorino/backend/Stop.java @@ -115,12 +115,16 @@ this.routesThatStopHere = routesThatStopHere; } + protected void setRoutesThatStopHereString(String routesStopping){ + this.routesThatStopHereString = routesStopping; + } + @Nullable protected List getRoutesThatStopHere(){ return routesThatStopHere; } - private @Nullable String buildRoutesString() { + protected @Nullable String buildRoutesString() { // no routes => no string if(this.routesThatStopHere == null || this.routesThatStopHere.size() == 0) { return null; diff --git a/src/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java b/src/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java --- a/src/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java +++ b/src/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java @@ -1,3 +1,20 @@ +/* + BusTO - Backend components + Copyright (C) 2021 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 . + */ package it.reyboz.bustorino.backend.gtfs; import android.content.Context; @@ -162,7 +179,7 @@ stream.close(); } - public static void readCSVWithColumns(BufferedReader reader, String tableName, Context con) throws IOException { + public static void readCSVWithColumns(BufferedReader reader, String tableName, Context con) { //String[] elements; List lineElements; @@ -255,7 +272,7 @@ public static List readCsvLine(String line) throws IllegalArgumentException { - List list = new ArrayList(); + List list = new ArrayList<>(); line += ","; for (int x = 0; x < line.length(); x++) 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 @@ -1,3 +1,20 @@ +/* + BusTO - Backend components + Copyright (C) 2021 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 . + */ package it.reyboz.bustorino.backend.mato; import android.util.Log; @@ -18,7 +35,9 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import it.reyboz.bustorino.backend.Fetcher; import it.reyboz.bustorino.backend.Palina; public class MapiArrivalRequest extends MapiVolleyRequest { @@ -26,9 +45,11 @@ private final String stopName; private final Date startingTime; private final int timeRange, numberOfDepartures; + private final AtomicReference reqRes; public MapiArrivalRequest(String stopName, Date startingTime, int timeRange, int numberOfDepartures, + AtomicReference res, Response.Listener listener, @Nullable Response.ErrorListener errorListener) { super(MatoAPIFetcher.QueryType.ARRIVALS, listener, errorListener); @@ -36,6 +57,14 @@ this.startingTime = startingTime; this.timeRange = timeRange; this.numberOfDepartures = numberOfDepartures; + this.reqRes = res; + } + public MapiArrivalRequest(String stopName, Date startingTime, int timeRange, + int numberOfDepartures, + Response.Listener listener, + @Nullable Response.ErrorListener errorListener) { + this(stopName, startingTime, timeRange, numberOfDepartures, + new AtomicReference<>(), listener, errorListener); } @Nullable @@ -52,7 +81,7 @@ data.put("variables", variables); - data.put("query", MatoAPIFetcher.QUERY_ARRIVALS); + data.put("query", MatoQueries.QUERY_ARRIVALS); } catch (JSONException e) { e.printStackTrace(); throw new AuthFailureError("Error with JSON enconding",e); @@ -65,8 +94,10 @@ @Override protected Response parseNetworkResponse(NetworkResponse response) { - if(response.statusCode != 200) - return Response.error(new VolleyError("Response Error Code "+response.statusCode)); + if(response.statusCode != 200) { + reqRes.set(Fetcher.Result.SERVER_ERROR); + return Response.error(new VolleyError("Response Error Code " + response.statusCode)); + } final String stringResponse = new String(response.data); Palina p = null; @@ -75,35 +106,44 @@ JSONArray allStopsFound = data.getJSONArray("stops"); - boolean haveManyResults = allStopsFound.length() > 1; + boolean stopFound = false; for (int i=0; i getParams() throws AuthFailureError { - return new HashMap<>(); + public StopNotFoundError(String message) { + super(message); + } + + public StopNotFoundError() { + super(); + } } } 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 @@ -24,10 +24,6 @@ } - @Nullable - @Override - abstract protected Map getParams() throws AuthFailureError; - @Override protected void deliverResponse(T response) { listener.onResponse(response); @@ -37,4 +33,5 @@ public Map getHeaders() throws AuthFailureError { return MatoAPIFetcher.Companion.getREQ_PARAMETERS(); } + } 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 @@ -1,3 +1,20 @@ +/* + BusTO - Backend components + Copyright (C) 2021 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 . + */ package it.reyboz.bustorino.backend.mato import android.content.Context @@ -15,49 +32,47 @@ -open class MatoAPIFetcher : ArrivalsFetcher { +open class MatoAPIFetcher(val minNumPassaggi: Int) : ArrivalsFetcher { var appContext: Context? = null set(value) { field = value!!.applicationContext } + constructor(): this(3) + override fun ReadArrivalTimesAll(stopID: String?, res: AtomicReference?): Palina { stopID!! val future = RequestFuture.newFuture() - val now = Calendar.getInstance().time; - var numMinutes = 30; + val now = Calendar.getInstance().time + var numMinutes = 30 var palina = Palina(stopID) var numPassaggi = 0 var trials = 0 - while (numPassaggi < 2 && trials < 4) { + while (numPassaggi < minNumPassaggi && trials < 4) { + numMinutes += 15 - val request = MapiArrivalRequest(stopID, now, numMinutes * 60, 10, future, future) + val request = MapiArrivalRequest(stopID, now, numMinutes * 60, 10, res, future, future) if (appContext == null || res == null) { Log.e("BusTO:MatoAPIFetcher", "ERROR: Given null context or null result ref") return Palina(stopID) } val requestQueue = NetworkVolleyManager.getInstance(appContext).requestQueue - request.setTag(VOLLEY_TAG) + request.setTag(getVolleyReqTag(QueryType.ARRIVALS)) requestQueue.add(request) - try { val palinaResult = future.get(5, TimeUnit.SECONDS) if (palinaResult!=null) { palina = palinaResult - if (palina.totalNumberOfPassages > 0) { - res.set(Fetcher.Result.OK) - } else res.set(Fetcher.Result.EMPTY_RESULT_SET) numPassaggi = palina.totalNumberOfPassages - } else{ - res.set(Fetcher.Result.EMPTY_RESULT_SET) } } catch (e: InterruptedException) { e.printStackTrace() res.set(Fetcher.Result.PARSER_ERROR) } catch (e: ExecutionException) { e.printStackTrace() + if (res.get() == Fetcher.Result.OK) res.set(Fetcher.Result.SERVER_ERROR) } catch (e: TimeoutException) { res.set(Fetcher.Result.CONNECTION_ERROR) @@ -84,6 +99,44 @@ "DNT" to "1", "Host" to "mapi.5t.torino.it") + fun getVolleyReqTag(type: QueryType): String{ + return when (type){ + QueryType.ALL_STOPS -> VOLLEY_TAG +"_AllStops" + QueryType.ARRIVALS -> VOLLEY_TAG+"_Arrivals" + } + } + + /** + * Get stops from the MatoAPI, set [res] accordingly + */ + fun getAllStopsGTT(context: Context, res: AtomicReference?): List{ + val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue + val future = RequestFuture.newFuture>() + + val request = VolleyAllStopsRequest(future, future) + request.tag = getVolleyReqTag(QueryType.ALL_STOPS) + + requestQueue.add(request) + + var palinaList:List = mutableListOf() + + try { + palinaList = future.get(30, TimeUnit.SECONDS) + + res?.set(Fetcher.Result.OK) + }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() + } + return palinaList + } + /* fun makeRequest(type: QueryType?, variables: JSONObject) : String{ type.let { val requestData = JSONObject() @@ -91,7 +144,7 @@ QueryType.ARRIVALS ->{ requestData.put("operationName","AllStopsDirect") requestData.put("variables", variables) - requestData.put("query", QUERY_ARRIVALS) + requestData.put("query", MatoQueries.QUERY_ARRIVALS) } else -> { //TODO all other cases @@ -106,6 +159,7 @@ } return "" } + */ fun parseStopJSON(jsonStop: JSONObject): Palina{ val latitude = jsonStop.getDouble("lat") val longitude = jsonStop.getDouble("lon") @@ -118,6 +172,7 @@ val routesStoppingJSON = jsonStop.getJSONArray("routes") val baseRoutes = mutableListOf() + // get all the possible routes for (i in 0 until routesStoppingJSON.length()){ val routeBaseInfo = routesStoppingJSON.getJSONObject(i) val r = Route(routeBaseInfo.getString("shortName"), Route.Type.UNKNOWN,"") @@ -125,20 +180,26 @@ baseRoutes.add(r) } - - val routesStopTimes = jsonStop.getJSONArray("stoptimesForPatterns") - - for (i in 0 until routesStopTimes.length()){ - val patternJSON = routesStopTimes.getJSONObject(i) - val mRoute = parseRouteStoptimesJSON(patternJSON) - - //val directionId = patternJSON.getJSONObject("pattern").getInt("directionId") - //TODO: use directionId - palina.addRoute(mRoute) - for (r in baseRoutes) { - if (palina.gtfsID != null && r.gtfsId.equals(palina.gtfsID)) { - baseRoutes.remove(r) - break + if (jsonStop.has("desc")){ + palina.location = jsonStop.getString("desc") + } + //there is also "zoneId" which is the zone of the stop (0-> city, etc) + + if(jsonStop.has("stoptimesForPatterns")) { + val routesStopTimes = jsonStop.getJSONArray("stoptimesForPatterns") + + for (i in 0 until routesStopTimes.length()) { + val patternJSON = routesStopTimes.getJSONObject(i) + val mRoute = parseRouteStoptimesJSON(patternJSON) + + //val directionId = patternJSON.getJSONObject("pattern").getInt("directionId") + //TODO: use directionId + palina.addRoute(mRoute) + for (r in baseRoutes) { + if (palina.gtfsID != null && r.gtfsId.equals(palina.gtfsID)) { + baseRoutes.remove(r) + break + } } } } @@ -146,13 +207,11 @@ palina.addRoute(noArrivalRoute) } //val gtfsRoutes = mutableListOf<>() - - return palina } fun parseRouteStoptimesJSON(jsonPatternWithStops: JSONObject): Route{ val patternJSON = jsonPatternWithStops.getJSONObject("pattern") - val routeJSON = patternJSON.getJSONObject("route"); + val routeJSON = patternJSON.getJSONObject("route") val passaggiJSON = jsonPatternWithStops.getJSONArray("stoptimes") val gtfsId = routeJSON.getString("gtfsId").trim() @@ -184,59 +243,19 @@ return route } - const val QUERY_ARRIVALS="""query AllStopsDirect( - ${'$'}name: String - ${'$'}startTime: Long - ${'$'}timeRange: Int - ${'$'}numberOfDepartures: Int - ) { - stops(name: ${'$'}name) { - __typename - lat - lon - gtfsId - code - name - desc - wheelchairBoarding - routes { - __typename - gtfsId - shortName - } - stoptimesForPatterns( - startTime: ${'$'}startTime - timeRange: ${'$'}timeRange - numberOfDepartures: ${'$'}numberOfDepartures - ) { - __typename - pattern { - __typename - headsign - directionId - route { - __typename - gtfsId - shortName - mode - } - } - stoptimes { - __typename - scheduledArrival - realtimeArrival - realtime - realtimeState - } - } - } - } - """ - } + fun makeRequestParameters(requestName:String, variables: JSONObject, query: String): JSONObject{ + val data = JSONObject() + data.put("operationName", requestName) + data.put("variables", variables) + data.put("query", query) + return data + } + + } enum class QueryType { - ARRIVALS, + ARRIVALS, ALL_STOPS } } \ 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 new file mode 100644 --- /dev/null +++ b/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt @@ -0,0 +1,90 @@ +/* + BusTO - Backend components + Copyright (C) 2021 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 . + */ +package it.reyboz.bustorino.backend.mato + +class MatoQueries { + + companion object{ + const val QUERY_ARRIVALS="""query AllStopsDirect( + ${'$'}name: String + ${'$'}startTime: Long + ${'$'}timeRange: Int + ${'$'}numberOfDepartures: Int + ) { + stops(name: ${'$'}name) { + __typename + lat + lon + gtfsId + code + name + desc + wheelchairBoarding + routes { + __typename + gtfsId + shortName + } + stoptimesForPatterns( + startTime: ${'$'}startTime + timeRange: ${'$'}timeRange + numberOfDepartures: ${'$'}numberOfDepartures + ) { + __typename + pattern { + __typename + headsign + directionId + route { + __typename + gtfsId + shortName + mode + } + } + stoptimes { + __typename + scheduledArrival + realtimeArrival + realtime + realtimeState + } + } + } + } + """ + + const val ALL_STOPS_BY_FEEDS=""" + query AllStops(${'$'}feeds: [String!]){ + stops(feeds: ${'$'}feeds) { + + lat + lon + gtfsId + code + name + desc + routes { + gtfsId + shortName + } + } + } + """ + } +} \ 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 new file mode 100644 --- /dev/null +++ b/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt @@ -0,0 +1,79 @@ +/* + BusTO - Backend components + Copyright (C) 2021 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 . + */ +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 it.reyboz.bustorino.backend.Palina +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +class VolleyAllStopsRequest( + listener: Response.Listener>, + errorListener: Response.ErrorListener, +) : MapiVolleyRequest>( + MatoAPIFetcher.QueryType.ALL_STOPS,listener, errorListener) { + private val FEEDS = JSONArray() + init { + + FEEDS.put("gtt") + } + override fun getBody(): ByteArray { + val variables = JSONObject() + variables.put("feeds", FEEDS) + + val data = MatoAPIFetcher.makeRequestParameters("AllStops", variables, MatoQueries.ALL_STOPS_BY_FEEDS) + + return data.toString().toByteArray() + } + + override fun parseNetworkResponse(response: NetworkResponse?): Response> { + 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 stringResponse = String(response.data) + val palinas = ArrayList() + + try { + val allData = JSONObject(stringResponse).getJSONObject("data") + val allStops = allData.getJSONArray("stops") + + for (i in 0 until allStops.length()){ + val jsonStop = allStops.getJSONObject(i) + palinas.add(MatoAPIFetcher.parseStopJSON(jsonStop)) + } + + } catch (e: JSONException){ + Log.e("VolleyBusTO","Cannot parse response as JSON") + e.printStackTrace() + return Response.error(VolleyError("Error parsing JSON")) + + } + return Response.success(palinas, HttpHeaderParser.parseCacheHeaders(response)) + } + companion object{ + 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 @@ -53,6 +53,8 @@ public static final String DEBUG_TAG = "Busto-UpdateWorker"; + private static final long UPDATE_MIN_DELAY= 3*7*24*3600; //3 weeks + public DBUpdateWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); @@ -72,9 +74,14 @@ final boolean isUpdateCompulsory = getInputData().getBoolean(FORCED_UPDATE,false); + final long lastDBUpdateTime = shPr.getLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, 0); + long currentTime = System.currentTimeMillis()/1000; + final int notificationID = showNotification(); Log.d(DEBUG_TAG, "Have previous version: "+current_DB_version +" and new version "+new_DB_version); Log.d(DEBUG_TAG, "Update compulsory: "+isUpdateCompulsory); + /* + SKIP CHECK (Reason: The Old API might fail at any moment) if (new_DB_version < 0){ //there has been an error final Data out = new Data.Builder().putInt(ERROR_REASON_KEY, ERROR_FETCHING_VERSION) @@ -82,9 +89,11 @@ cancelNotification(notificationID); return ListenableWorker.Result.failure(out); } + */ //we got a good version - if (current_DB_version >= new_DB_version && !isUpdateCompulsory) { + if (!(current_DB_version < new_DB_version || currentTime > lastDBUpdateTime + UPDATE_MIN_DELAY ) + && !isUpdateCompulsory) { //don't need to update cancelNotification(notificationID); return ListenableWorker.Result.success(new Data.Builder(). @@ -115,6 +124,8 @@ //update the version in the shared preference final SharedPreferences.Editor editor = shPr.edit(); editor.putInt(DatabaseUpdate.DB_VERSION_KEY, new_DB_version); + currentTime = System.currentTimeMillis()/1000; + editor.putLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, currentTime); editor.apply(); cancelNotification(notificationID); 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 @@ -27,12 +27,18 @@ 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 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; @@ -41,13 +47,15 @@ public class DatabaseUpdate { - public static final String DEBUG_TAG = "BusTO-DBUpdate"; - public static final int VERSION_UNAVAILABLE = -2; - public static final int JSON_PARSING_ERROR = -4; + public static final String DEBUG_TAG = "BusTO-DBUpdate"; + public static final int VERSION_UNAVAILABLE = -2; + public static final int JSON_PARSING_ERROR = -4; + + public static final String DB_VERSION_KEY = "NextGenDB.GTTVersion"; + public static final String DB_LAST_UPDATE_KEY = "NextGenDB.LastDBUpdate"; - public static final String DB_VERSION_KEY = "NextGenDB.GTTVersion"; - enum Result { + enum Result { DONE, ERROR_STOPS_DOWNLOAD, ERROR_LINES_DOWNLOAD } @@ -80,6 +88,7 @@ public static Result performDBUpdate(Context con, AtomicReference gres) { final FiveTAPIFetcher f = new FiveTAPIFetcher(); + /* final ArrayList stops = f.getAllStopsFromGTT(gres); //final ArrayList cpOp = new ArrayList<>(); @@ -88,9 +97,18 @@ return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD; } - // return false; //If the commit to the SharedPreferences didn't succeed, simply stop updating the database + + */ final NextGenDB dbHelp = new NextGenDB(con.getApplicationContext()); final SQLiteDatabase db = dbHelp.getWritableDatabase(); + + final List 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); @@ -99,20 +117,20 @@ //put new data long startTime = System.currentTimeMillis(); - Log.d(DEBUG_TAG, "Inserting " + stops.size() + " stops"); - for (final Stop s : stops) { + 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, s.ID); - cv.put(NextGenDB.Contract.StopsTable.COL_NAME, s.getStopDefaultName()); - if (s.location != null) - cv.put(NextGenDB.Contract.StopsTable.COL_LOCATION, s.location); - cv.put(NextGenDB.Contract.StopsTable.COL_LAT, s.getLatitude()); - cv.put(NextGenDB.Contract.StopsTable.COL_LONG, s.getLongitude()); - if (s.getAbsurdGTTPlaceName() != null) cv.put(NextGenDB.Contract.StopsTable.COL_PLACE, s.getAbsurdGTTPlaceName()); - cv.put(NextGenDB.Contract.StopsTable.COL_LINES_STOPPING, s.routesThatStopHereToString()); - if (s.type != null) cv.put(NextGenDB.Contract.StopsTable.COL_TYPE, s.type.getCode()); - + 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; @@ -184,16 +202,23 @@ public static void requestDBUpdateWithWork(Context con, boolean forced){ final SharedPreferences theShPr = PreferencesHolder.getMainSharedPreferences(con); final WorkManager workManager = WorkManager.getInstance(con); - PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 1, TimeUnit.DAYS) + PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 7, TimeUnit.DAYS) .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES) .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED) .build()) .build(); final int version = theShPr.getInt(DatabaseUpdate.DB_VERSION_KEY, -10); - if (version >= 0 && !forced) + final long lastDBUpdateTime = theShPr.getLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, -10); + if ((version >= 0 || lastDBUpdateTime >=0) && !forced) workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG, ExistingPeriodicWorkPolicy.KEEP, wr); else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG, ExistingPeriodicWorkPolicy.REPLACE, wr); } + /* + public static boolean isDBUpdating(){ + return false; + TODO + } + */ } diff --git a/src/it/reyboz/bustorino/data/NextGenDB.java b/src/it/reyboz/bustorino/data/NextGenDB.java --- a/src/it/reyboz/bustorino/data/NextGenDB.java +++ b/src/it/reyboz/bustorino/data/NextGenDB.java @@ -36,7 +36,7 @@ public class NextGenDB extends SQLiteOpenHelper{ public static final String DATABASE_NAME = "bustodatabase.db"; - public static final int DATABASE_VERSION = 2; + public static final int DATABASE_VERSION = 3; public static final String DEBUG_TAG = "NextGenDB-BusTO"; //NO Singleton instance //private static volatile NextGenDB instance = null; @@ -67,6 +67,7 @@ private static final String SQL_CREATE_STOPS_TABLE="CREATE TABLE "+Contract.StopsTable.TABLE_NAME+" ("+ Contract.StopsTable.COL_ID+" TEXT PRIMARY KEY, "+ Contract.StopsTable.COL_TYPE+" INTEGER, "+Contract.StopsTable.COL_LAT+" REAL NOT NULL, "+ Contract.StopsTable.COL_LONG+" REAL NOT NULL, "+ Contract.StopsTable.COL_NAME+" TEXT NOT NULL, "+ + StopsTable.COL_GTFS_ID+" TEXT, "+ Contract.StopsTable.COL_LOCATION+" TEXT, "+Contract.StopsTable.COL_PLACE+" TEXT, "+ Contract.StopsTable.COL_LINES_STOPPING +" TEXT )"; @@ -77,7 +78,7 @@ Contract.StopsTable.COL_LINES_STOPPING +" TEXT )"; public static final String[] QUERY_COLUMN_stops_all = { - StopsTable.COL_ID, StopsTable.COL_NAME, StopsTable.COL_LOCATION, + StopsTable.COL_ID, StopsTable.COL_NAME, StopsTable.COL_GTFS_ID, StopsTable.COL_LOCATION, StopsTable.COL_TYPE, StopsTable.COL_LAT, StopsTable.COL_LONG, StopsTable.COL_LINES_STOPPING}; public static final String QUERY_WHERE_LAT_AND_LNG_IN_RANGE = StopsTable.COL_LAT + " >= ? AND " + @@ -125,6 +126,14 @@ DatabaseUpdate.requestDBUpdateWithWork(appContext, true); } + if(oldVersion < 3 && newVersion == 3){ + Log.d("BusTO-Database", "Running upgrades for version 3"); + //add the new column + db.execSQL("ALTER TABLE "+StopsTable.TABLE_NAME+ + " ADD COLUMN "+StopsTable.COL_GTFS_ID+" TEXT "); + + // DatabaseUpdate.requestDBUpdateWithWork(appContext, true); + } } @Override @@ -209,7 +218,9 @@ while(result.moveToNext()) { final String stopID = result.getString(colID).trim(); - final Route.Type type = Route.getTypeFromSymbol(result.getString(colType)); + final Route.Type type; + if(result.getString(colType) == null) type = Route.Type.BUS; + else type = Route.getTypeFromSymbol(result.getString(colType)); String lines = result.getString(colLines).trim(); String locationSometimesEmpty = result.getString(colLocation); @@ -252,6 +263,10 @@ return success; } + int updateLinesStoppingInStop(List stops){ + return 0; + } + public static List splitLinesString(String linesStr){ return Arrays.asList(linesStr.split("\\s*,\\s*")); } @@ -330,19 +345,21 @@ public static final String COL_ID = "stopid"; //integer public static final String COL_TYPE = "stop_type"; public static final String COL_NAME = "stop_name"; + public static final String COL_GTFS_ID = "gtfs_id"; public static final String COL_LAT = "stop_latitude"; public static final String COL_LONG = "stop_longitude"; public static final String COL_LOCATION = "stop_location"; public static final String COL_PLACE = "stop_placeName"; public static final String COL_LINES_STOPPING = "stop_lines"; + @Override public String getTableName() { return TABLE_NAME; } @Override public String[] getFields() { - return new String[]{COL_ID,COL_TYPE,COL_NAME,COL_LAT,COL_LONG,COL_LOCATION,COL_PLACE,COL_LINES_STOPPING}; + return new String[]{COL_ID,COL_TYPE,COL_NAME,COL_GTFS_ID,COL_LAT,COL_LONG,COL_LOCATION,COL_PLACE,COL_LINES_STOPPING}; } } } diff --git a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java --- a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java +++ b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java @@ -271,8 +271,8 @@ } return v; } - public Fetcher[] getCurrentFetchersAsArray(){ - Fetcher[] arr = new Fetcher[fetchers.size()]; + public ArrivalsFetcher[] getCurrentFetchersAsArray(){ + ArrivalsFetcher[] arr = new ArrivalsFetcher[fetchers.size()]; fetchers.toArray(arr); return arr; } diff --git a/src/it/reyboz/bustorino/fragments/FragmentHelper.java b/src/it/reyboz/bustorino/fragments/FragmentHelper.java --- a/src/it/reyboz/bustorino/fragments/FragmentHelper.java +++ b/src/it/reyboz/bustorino/fragments/FragmentHelper.java @@ -23,6 +23,8 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; + +import android.os.AsyncTask; import android.util.Log; import android.widget.Toast; @@ -50,7 +52,8 @@ private final Context context; public static final int NO_FRAME = -3; private static final String DEBUG_TAG = "BusTO FragmHelper"; - private WeakReference lastTaskRef; + private WeakReference lastTaskRef; + private SearchRequestType lastTaskType; private boolean shouldHaltAllActivities=false; @@ -79,8 +82,8 @@ this.lastSuccessfullySearchedBusStop = stop; } - public void setLastTaskRef(WeakReference lastTaskRef) { - this.lastTaskRef = lastTaskRef; + public void setLastTaskRef(AsyncTask task) { + this.lastTaskRef = new WeakReference<>(task); } /** @@ -199,7 +202,7 @@ public void stopLastRequestIfNeeded(boolean interruptIfRunning){ if(lastTaskRef == null) return; - AsyncDataDownload task = lastTaskRef.get(); + AsyncTask task = lastTaskRef.get(); if(task!=null){ task.cancel(interruptIfRunning); } @@ -209,8 +212,9 @@ * Wrapper to show the errors/status that happened * @param res result from Fetcher */ - public void showErrorMessage(Fetcher.Result res){ + public void showErrorMessage(Fetcher.Result res, SearchRequestType type){ //TODO: implement a common set of errors for all fragments + Log.d(DEBUG_TAG, "Showing result for "+res); switch (res){ case OK: break; @@ -231,6 +235,13 @@ showShortToast(R.string.query_too_short); break; case EMPTY_RESULT_SET: + if (type == SearchRequestType.STOPS) + showShortToast(R.string.no_bus_stop_have_this_name); + else if(type == SearchRequestType.ARRIVALS){ + showShortToast(R.string.no_arrivals_stop); + } + break; + case NOT_FOUND: showShortToast(R.string.no_bus_stop_have_this_name); break; } diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java --- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -44,7 +44,8 @@ import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.*; import it.reyboz.bustorino.middleware.AppLocationManager; -import it.reyboz.bustorino.middleware.AsyncDataDownload; +import it.reyboz.bustorino.middleware.AsyncArrivalsSearcher; +import it.reyboz.bustorino.middleware.AsyncStopsSearcher; import it.reyboz.bustorino.util.LocationCriteria; import it.reyboz.bustorino.util.Permissions; @@ -106,10 +107,10 @@ } else{ String stopName = fragment.getStopID(); - new AsyncDataDownload(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName); + new AsyncArrivalsSearcher(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName); } } else //we create a new fragment, which is WRONG - new AsyncDataDownload(fragmentHelper, arrivalsFetchers, getContext()).execute(); + new AsyncArrivalsSearcher(fragmentHelper, arrivalsFetchers, getContext()).execute(); } }; @@ -466,7 +467,7 @@ } else { fragmentHelper.stopLastRequestIfNeeded(true); - new AsyncDataDownload(fragmentHelper, stopsFinderByNames, getContext()).execute(query); + new AsyncStopsSearcher(fragmentHelper, stopsFinderByNames).execute(query); } } } @@ -660,13 +661,13 @@ if (fragment != null && fragment.getStopID() != null && fragment.getStopID().equals(ID)){ // Run with previous fetchers //fragment.getCurrentFetchers().toArray() - new AsyncDataDownload(fragmentHelper,fragment.getCurrentFetchersAsArray(), getContext()).execute(ID); + new AsyncArrivalsSearcher(fragmentHelper,fragment.getCurrentFetchersAsArray(), getContext()).execute(ID); } else{ - new AsyncDataDownload(fragmentHelper, arrivalsFetchers, getContext()).execute(ID); + new AsyncArrivalsSearcher(fragmentHelper, arrivalsFetchers, getContext()).execute(ID); } } else { - new AsyncDataDownload(fragmentHelper,arrivalsFetchers, getContext()).execute(ID); + new AsyncArrivalsSearcher(fragmentHelper,arrivalsFetchers, getContext()).execute(ID); Log.d(DEBUG_TAG, "Started search for arrivals of stop " + ID); } } diff --git a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java b/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java rename from src/it/reyboz/bustorino/middleware/AsyncDataDownload.java rename to src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java --- a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java +++ b/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java @@ -49,27 +49,25 @@ /** * This should be used to download data, but not to display it */ -public class AsyncDataDownload extends AsyncTask{ +public class AsyncArrivalsSearcher extends AsyncTask{ private static final String TAG = "BusTO-DataDownload"; private static final String DEBUG_TAG = TAG; private boolean failedAll = false; private final AtomicReference res; - private final RequestType t; private String query; WeakReference helperRef; private final ArrayList otherActivities = new ArrayList<>(); - private final Fetcher[] theFetchers; + private final ArrivalsFetcher[] theFetchers; @SuppressLint("StaticFieldLeak") private final Context context; private final boolean replaceFragment; - public AsyncDataDownload(FragmentHelper fh, @NonNull Fetcher[] fetchers, Context context) { - RequestType type; + public AsyncArrivalsSearcher(FragmentHelper fh, @NonNull ArrivalsFetcher[] fetchers, Context context) { helperRef = new WeakReference<>(fh); - fh.setLastTaskRef(new WeakReference<>(this)); + fh.setLastTaskRef(this); res = new AtomicReference<>(); this.context = context.getApplicationContext(); this.replaceFragment = true; @@ -78,22 +76,13 @@ if (theFetchers.length < 1){ throw new IllegalArgumentException("You have to put at least one Fetcher, idiot!"); } - if (theFetchers[0] instanceof ArrivalsFetcher){ - type = RequestType.ARRIVALS; - } else if (theFetchers[0] instanceof StopsFinderByName){ - type = RequestType.STOPS; - } else{ - type = null; - } - t = type; } @Override - protected Object doInBackground(String... params) { - RecursionHelper r = new RecursionHelper<>(theFetchers); - boolean success=false; - Object result; + protected Palina doInBackground(String... params) { + RecursionHelper r = new RecursionHelper<>(theFetchers); + Palina result = null; FragmentHelper fh = helperRef.get(); ArrayList results = new ArrayList<>(theFetchers.length); //If the FragmentHelper is null, that means the activity doesn't exist anymore @@ -107,87 +96,76 @@ return null; } //get the data from the fetcher - switch (t){ - case ARRIVALS: - ArrivalsFetcher f = (ArrivalsFetcher) r.getAndMoveForward(); - if (f instanceof MatoAPIFetcher){ - ((MatoAPIFetcher)f).setAppContext(context); - } - Log.d(TAG,"Using the ArrivalsFetcher: "+f.getClass()); - - Stop lastSearchedBusStop = fh.getLastSuccessfullySearchedBusStop(); - Palina p; - String stopID; - if(params.length>0) - stopID=params[0]; //(it's a Palina) - else if(lastSearchedBusStop!=null) - stopID = lastSearchedBusStop.ID; //(it's a Palina) - else { - publishProgress(Fetcher.Result.QUERY_TOO_SHORT); - return null; - } - //Skip the FiveTAPIFetcher for the Metro Stops because it shows incomprehensible arrival times - try { - if (f instanceof FiveTAPIFetcher && Integer.parseInt(stopID) >= 8200) - continue; - } catch (NumberFormatException ex){ - Log.e(DEBUG_TAG, "The stop number is not a valid integer, expect failures"); - } - p= f.ReadArrivalTimesAll(stopID,res); - - //if (res.get()!= Fetcher.Result.OK) - Log.d(DEBUG_TAG, "Arrivals fetcher: "+f+"\n\tProgress: "+res.get()); - - - if(f instanceof FiveTAPIFetcher){ - AtomicReference gres = new AtomicReference<>(); - List branches = ((FiveTAPIFetcher) f).getDirectionsForStop(stopID,gres); - Log.d(DEBUG_TAG, "FiveTArrivals fetcher: "+f+"\n\tDetails req: "+gres.get()); - if(gres.get() == Fetcher.Result.OK){ - p.addInfoFromRoutes(branches); - Thread t = new Thread(new BranchInserter(branches, context)); - t.start(); - otherActivities.add(t); - - } - //put updated values into Database - } + ArrivalsFetcher f = r.getAndMoveForward(); + AtomicReference resRef = new AtomicReference<>(); + if (f instanceof MatoAPIFetcher){ + ((MatoAPIFetcher)f).setAppContext(context); + } + Log.d(TAG,"Using the ArrivalsFetcher: "+f.getClass()); + + Stop lastSearchedBusStop = fh.getLastSuccessfullySearchedBusStop(); + Palina p; + String stopID; + if(params.length>0) + stopID=params[0]; //(it's a Palina) + else if(lastSearchedBusStop!=null) + stopID = lastSearchedBusStop.ID; //(it's a Palina) + else { + publishProgress(Fetcher.Result.QUERY_TOO_SHORT); + return null; + } + //Skip the FiveTAPIFetcher for the Metro Stops because it shows incomprehensible arrival times + try { + if (f instanceof FiveTAPIFetcher && Integer.parseInt(stopID) >= 8200) + continue; + } catch (NumberFormatException ex){ + Log.e(DEBUG_TAG, "The stop number is not a valid integer, expect failures"); + } + p= f.ReadArrivalTimesAll(stopID,resRef); + + + //if (res.get()!= Fetcher.Result.OK) + Log.d(DEBUG_TAG, "Arrivals fetcher: "+f+"\n\tProgress: "+resRef.get()); + + if(f instanceof FiveTAPIFetcher){ + AtomicReference gres = new AtomicReference<>(); + List branches = ((FiveTAPIFetcher) f).getDirectionsForStop(stopID,gres); + Log.d(DEBUG_TAG, "FiveTArrivals fetcher: "+f+"\n\tDetails req: "+gres.get()); + if(gres.get() == Fetcher.Result.OK){ + p.addInfoFromRoutes(branches); + Thread t = new Thread(new BranchInserter(branches, context)); + t.start(); + otherActivities.add(t); + } else{ + resRef.set(Fetcher.Result.NOT_FOUND); + } + //put updated values into Database + } - if(lastSearchedBusStop != null && res.get()== Fetcher.Result.OK) { - // check that we don't have the same stop - if(lastSearchedBusStop.ID.equals(p.ID)) { - // searched and it's the same - String sn = lastSearchedBusStop.getStopDisplayName(); - if(sn != null) { - // "merge" Stop over Palina and we're good to go - p.mergeNameFrom(lastSearchedBusStop); - } - } + if(lastSearchedBusStop != null && resRef.get()== Fetcher.Result.OK) { + // check that we don't have the same stop + if(lastSearchedBusStop.ID.equals(p.ID)) { + // searched and it's the same + String sn = lastSearchedBusStop.getStopDisplayName(); + if(sn != null) { + // "merge" Stop over Palina and we're good to go + p.mergeNameFrom(lastSearchedBusStop); } - p.mergeDuplicateRoutes(0); - if (p.getTotalNumberOfPassages() == 0) - res.set(Fetcher.Result.EMPTY_RESULT_SET); - publishProgress(res.get()); - //p.sortRoutes(); - result = p; - - //TODO: find a way to avoid overloading the user with toasts - break; - case STOPS: - StopsFinderByName finder = (StopsFinderByName) r.getAndMoveForward(); - - List resultList= finder.FindByName(params[0], this.res); //it's a List - Log.d(TAG,"Using the StopFinderByName: "+finder.getClass()); - query =params[0]; - result = resultList; //dummy result - Log.d(DEBUG_TAG, "Result: "+res.get()+", "+resultList.size()+" stops"); - break; - default: - result = null; + } + } + p.mergeDuplicateRoutes(0); + if (resRef.get() == Fetcher.Result.OK && p.getTotalNumberOfPassages() == 0 ) { + resRef.set(Fetcher.Result.EMPTY_RESULT_SET); + Log.d(DEBUG_TAG, "Setting empty results"); + } + publishProgress(resRef.get()); + //TODO: find a way to avoid overloading the user with toasts + if (result == null){ + result = p; } //find if it went well - results.add(res.get()); - if(res.get()== Fetcher.Result.OK) { + results.add(resRef.get()); + if(resRef.get()== Fetcher.Result.OK) { //wait for other threads to finish for(Thread t: otherActivities){ try { @@ -196,7 +174,7 @@ //do nothing } } - return result; + return p; } } @@ -207,14 +185,10 @@ break; } } - if(emptyResults){ - if(t==RequestType.STOPS) - publishProgress(Fetcher.Result.EMPTY_RESULT_SET); - } //at this point, we are sure that the result has been negative failedAll=true; - return null; + return result; } @Override @@ -223,7 +197,7 @@ if (fh!=null) for (Fetcher.Result r : values){ //TODO: make Toast - fh.showErrorMessage(r); + fh.showErrorMessage(r, SearchRequestType.ARRIVALS); } else { Log.w(TAG,"We had to show some progress but activity was destroyed"); @@ -231,10 +205,10 @@ } @Override - protected void onPostExecute(Object o) { + protected void onPostExecute(Palina p) { FragmentHelper fh = helperRef.get(); - if(failedAll || o == null || fh == null){ + if(failedAll || p == null || fh == null){ //everything went bad if(fh!=null) fh.toggleSpinner(false); cancel(true); @@ -244,37 +218,8 @@ if(isCancelled()) return; - switch (t){ - case ARRIVALS: - Palina palina = (Palina) o; - fh.createOrUpdateStopFragment(palina, replaceFragment); - break; - case STOPS: - //this should never be a problem - if(!(o instanceof List)){ - throw new IllegalStateException(); - } - List list = (List) o; - if (list.size() ==0) return; - Object firstItem = list.get(0); - if(!(firstItem instanceof Stop)) return; - ArrayList stops = new ArrayList<>(); - for(Object x: list){ - if(x instanceof Stop) stops.add((Stop) x); - //Log.d(DEBUG_TAG, "Parsing Stop: "+x); - } - if(list.size() != stops.size()){ - Log.w(DEBUG_TAG, "Wrong stop list size:\n incoming: "+ - list.size()+" out: "+stops.size()); - } - //List stopList = (List) list; - if(query!=null && !isCancelled()) { - fh.createStopListFragment(stops,query, replaceFragment); - } else Log.e(TAG,"QUERY NULL, COULD NOT CREATE FRAGMENT"); - break; - case DBUPDATE: - break; - } + + fh.createOrUpdateStopFragment( p, replaceFragment); } @Override @@ -290,10 +235,6 @@ } - public enum RequestType { - ARRIVALS,STOPS,DBUPDATE - } - public static class BranchInserter implements Runnable{ private final List routesToInsert; diff --git a/src/it/reyboz/bustorino/middleware/AsyncStopsSearcher.java b/src/it/reyboz/bustorino/middleware/AsyncStopsSearcher.java new file mode 100644 --- /dev/null +++ b/src/it/reyboz/bustorino/middleware/AsyncStopsSearcher.java @@ -0,0 +1,131 @@ +/* + BusTO (middleware) + Copyright (C) 2021 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 . + */ +package it.reyboz.bustorino.middleware; + +import android.os.AsyncTask; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import it.reyboz.bustorino.backend.Fetcher; +import it.reyboz.bustorino.backend.Stop; +import it.reyboz.bustorino.backend.StopsFinderByName; +import it.reyboz.bustorino.fragments.FragmentHelper; + +public class AsyncStopsSearcher extends AsyncTask> { + + private static final String TAG = "BusTO-StopsSearcher"; + private static final String DEBUG_TAG = TAG; + private final StopsFinderByName[] fetchers; + private final AtomicReference res; + + private WeakReference helperWR; + + private String theQuery; + + public AsyncStopsSearcher(FragmentHelper fh, StopsFinderByName[] fetchers) { + this.fetchers = fetchers; + if (fetchers.length < 1){ + throw new IllegalArgumentException("You have to put at least one Fetcher, idiot!"); + } + + this.res = new AtomicReference<>(); + this.helperWR = new WeakReference<>(fh); + fh.setLastTaskRef(this); + + } + + @Override + protected List doInBackground(String... strings) { + RecursionHelper r = new RecursionHelper<>(fetchers); + if (helperWR.get()==null || strings.length == 0) + return null; + Log.d(DEBUG_TAG,"Running with query "+strings[0]); + + ArrayList results = new ArrayList<>(); + List resultsList; + while (r.valid()){ + if (this.isCancelled()) return null; + + final StopsFinderByName finder = r.getAndMoveForward(); + theQuery = strings[0].trim(); + resultsList = finder.FindByName(theQuery, res); + Log.d(DEBUG_TAG, "Result: "+res.get()+", "+resultsList.size()+" stops"); + + if (res.get()== Fetcher.Result.OK){ + return resultsList; + } + results.add(res.get()); + } + boolean emptyResults = true; + for (Fetcher.Result re: results){ + if (!re.equals(Fetcher.Result.EMPTY_RESULT_SET)) { + emptyResults = false; + break; + } + } + if(emptyResults){ + publishProgress(Fetcher.Result.EMPTY_RESULT_SET); + } + return new ArrayList<>(); + } + + @Override + protected void onProgressUpdate(Fetcher.Result... values) { + FragmentHelper fh = helperWR.get(); + if (fh!=null) + for (Fetcher.Result r : values){ + fh.showErrorMessage(r, SearchRequestType.STOPS); + } + else { + Log.w(TAG,"We had to show some progress but activity was destroyed"); + } + } + @Override + protected void onCancelled() { + FragmentHelper fh = helperWR.get(); + if (fh!=null) fh.toggleSpinner(false); + } + + @Override + protected void onPreExecute() { + FragmentHelper fh = helperWR.get(); + if (fh!=null) fh.toggleSpinner(true); + } + + @Override + protected void onPostExecute(List stops) { + final FragmentHelper fh = helperWR.get(); + + if (stops==null || fh==null || theQuery==null) { + if (fh!=null) fh.toggleSpinner(false); + cancel(true); + return; + } + if(isCancelled()){ + fh.toggleSpinner(false); + return; + } + fh.createStopListFragment(stops, theQuery, true); + + + } +} diff --git a/src/it/reyboz/bustorino/middleware/SearchRequestType.java b/src/it/reyboz/bustorino/middleware/SearchRequestType.java new file mode 100644 --- /dev/null +++ b/src/it/reyboz/bustorino/middleware/SearchRequestType.java @@ -0,0 +1,5 @@ +package it.reyboz.bustorino.middleware; + +public enum SearchRequestType { + ARRIVALS,STOPS,DBUPDATE +} diff --git a/src/it/reyboz/bustorino/adapters/RouteSorterByArrivalTime.kt b/src/it/reyboz/bustorino/util/RouteSorterByArrivalTime.kt rename from src/it/reyboz/bustorino/adapters/RouteSorterByArrivalTime.kt rename to src/it/reyboz/bustorino/util/RouteSorterByArrivalTime.kt --- a/src/it/reyboz/bustorino/adapters/RouteSorterByArrivalTime.kt +++ b/src/it/reyboz/bustorino/util/RouteSorterByArrivalTime.kt @@ -1,4 +1,4 @@ -package it.reyboz.bustorino.adapters +package it.reyboz.bustorino.util import it.reyboz.bustorino.backend.Route @@ -7,9 +7,9 @@ override fun compare(route1: Route?, route2: Route?): Int { if (route1 == null){ if(route2 == null) return 0 - else return 2; + else return 2 } else if (route2 == null){ - return -2; + return -2 } val passaggi1 = route1.passaggi val passaggi2 = route2.passaggi