diff --git a/src/it/reyboz/bustorino/backend/GlobalStatusPreferences.java b/src/it/reyboz/bustorino/backend/GlobalStatusPreferences.java
new file mode 100644
index 0000000..a29dd59
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/GlobalStatusPreferences.java
@@ -0,0 +1,76 @@
+/*
+ BusTO - Backend components
+ Copyright (C) 2018 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;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class GlobalStatusPreferences {
+ private static final String PREFERENCES_NAME = "it.reyboz.bustorino.statusPreferences";
+ private static final String DB_UPDATING = "DB_updating";
+
+ private Context thecon;
+ private SharedPreferences preferences;
+ private SharedPreferences.OnSharedPreferenceChangeListener sharedPrefListener;
+
+ public GlobalStatusPreferences(Context thecon) {
+ this.thecon = thecon;
+ this.preferences = thecon.getSharedPreferences(PREFERENCES_NAME,Context.MODE_PRIVATE);
+ }
+
+ public boolean isDBUpdating(){
+ if (preferences == null) preferences = thecon.getSharedPreferences(PREFERENCES_NAME,Context.MODE_PRIVATE);
+ return preferences.getBoolean(DB_UPDATING,false);
+ }
+
+ public void registerListener(OnDBStatusChangedListener listener){
+ if(sharedPrefListener!=null) unregisterListener();
+ sharedPrefListener = new FinishedUpdateListener(listener);
+ preferences.registerOnSharedPreferenceChangeListener(sharedPrefListener);
+ }
+
+ public void unregisterListener(){
+ preferences.unregisterOnSharedPreferenceChangeListener(sharedPrefListener);
+ }
+ public void setDbUpdating(boolean value){
+ final SharedPreferences.Editor editor = preferences.edit();
+ editor.putBoolean(DB_UPDATING,value);
+ editor.apply();
+ }
+ /**
+ * Probably useless
+ */
+ public class FinishedUpdateListener implements SharedPreferences.OnSharedPreferenceChangeListener{
+ private OnDBStatusChangedListener thingToDo;
+
+ public FinishedUpdateListener(OnDBStatusChangedListener thingToDo) {
+ this.thingToDo = thingToDo;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if(key.equals(DB_UPDATING)){
+ thingToDo.onDBUpdateStatusChanged(sharedPreferences.getBoolean(DB_UPDATING,true));
+ }
+ }
+ }
+ public interface OnDBStatusChangedListener {
+ void onDBUpdateStatusChanged(boolean isUpdating);
+ }
+
+}
diff --git a/src/it/reyboz/bustorino/middleware/AppDataProvider.java b/src/it/reyboz/bustorino/middleware/AppDataProvider.java
index 0389117..39dbfc5 100644
--- a/src/it/reyboz/bustorino/middleware/AppDataProvider.java
+++ b/src/it/reyboz/bustorino/middleware/AppDataProvider.java
@@ -1,251 +1,266 @@
/*
BusTO (middleware)
Copyright (C) 2018 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.content.ContentProvider;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.UriMatcher;
+import android.content.*;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
+import it.reyboz.bustorino.R;
+import it.reyboz.bustorino.backend.GlobalStatusPreferences;
import it.reyboz.bustorino.middleware.NextGenDB.Contract.*;
import java.util.List;
public class AppDataProvider extends ContentProvider {
public static final String AUTHORITY = "it.reyboz.bustorino.provider";
private static final int STOP_OP = 1;
private static final int LINE_OP = 2;
private static final int BRANCH_OP = 3;
private static final int FAVORITES_OP =4;
private static final int MANY_STOPS = 5;
private static final int ADD_UPDATE_BRANCHES = 6;
private static final int LINE_INSERT_OP = 7;
private static final int CONNECTIONS = 8;
private static final int LOCATION_SEARCH = 9;
private static final String DEBUG_TAG="AppDataProvider";
-
+ private Context con;
private NextGenDB appDBHelper;
private UserDB udbhelper;
private SQLiteDatabase db;
-
+ private GlobalStatusPreferences preferences;
public AppDataProvider() {
}
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
/*
* The calls to addURI() go here, for all of the content URI patterns that the provider
* should recognize.
*/
sUriMatcher.addURI(AUTHORITY, "stop/#", STOP_OP);
sUriMatcher.addURI(AUTHORITY,"stops",MANY_STOPS);
sUriMatcher.addURI(AUTHORITY,"stops/location/*/*/*",LOCATION_SEARCH);
/*
* Sets the code for a single row to 2. In this case, the "#" wildcard is
* used. "content://com.example.app.provider/table3/3" matches, but
* "content://com.example.app.provider/table3 doesn't.
*/
sUriMatcher.addURI(AUTHORITY, "line/#", LINE_OP);
sUriMatcher.addURI(AUTHORITY,"branch/#",BRANCH_OP);
sUriMatcher.addURI(AUTHORITY,"line/insert",LINE_INSERT_OP);
sUriMatcher.addURI(AUTHORITY,"branches",ADD_UPDATE_BRANCHES);
sUriMatcher.addURI(AUTHORITY,"connections",CONNECTIONS);
sUriMatcher.addURI(AUTHORITY,"favorites/#",FAVORITES_OP);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
db = appDBHelper.getWritableDatabase();
int rows;
switch (sUriMatcher.match(uri)){
case MANY_STOPS:
rows = db.delete(NextGenDB.Contract.StopsTable.TABLE_NAME,null,null);
break;
default:
throw new UnsupportedOperationException("Not yet implemented");
}
return rows;
}
@Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
int match = sUriMatcher.match(uri);
String baseTypedir = "vnd.android.cursor.dir/";
String baseTypeitem = "vnd.android.cursor.item/";
switch (match){
case LOCATION_SEARCH:
return baseTypedir+"stop";
case LINE_OP:
return baseTypeitem+"line";
case CONNECTIONS:
return baseTypedir+"stops";
}
return baseTypedir+"/item";
}
@Override
public Uri insert(Uri uri, ContentValues values) throws IllegalArgumentException{
+ //AVOID OPENING A DB CONNECTION, WILL THROW VERY NASTY ERRORS
+ if(preferences.isDBUpdating())
+ return null;
db = appDBHelper.getWritableDatabase();
- Uri finalUri = null;
+ Uri finalUri;
long last_rowid = -1;
switch (sUriMatcher.match(uri)){
case ADD_UPDATE_BRANCHES:
Log.d("InsBranchWithProvider","new Insert request");
String line_name = values.getAsString(NextGenDB.Contract.LinesTable.COLUMN_NAME);
if(line_name==null) throw new IllegalArgumentException("No line name given");
long lineid = -1;
Cursor c = db.query(LinesTable.TABLE_NAME,
new String[]{LinesTable._ID,LinesTable.COLUMN_NAME,LinesTable.COLUMN_DESCRIPTION},NextGenDB.Contract.LinesTable.COLUMN_NAME +" =?",
new String[]{line_name},null,null,null);
Log.d("InsBranchWithProvider","finding line in the database: "+c.getCount()+" matches");
if(c.getCount() == 0){
//There are no lines, insert?
//NOPE
/*
c.close();
ContentValues cv = new ContentValues();
cv.put(LinesTable.COLUMN_NAME,line_name);
lineid = db.insert(LinesTable.TABLE_NAME,null,cv);
*/
break;
}else {
c.moveToFirst();
/*
while(c.moveToNext()){
Log.d("InsBranchWithProvider","line: "+c.getString(c.getColumnIndex(LinesTable.COLUMN_NAME))+"\n"
+c.getString(c.getColumnIndex(LinesTable.COLUMN_DESCRIPTION)));
}*/
lineid = c.getInt(c.getColumnIndex(NextGenDB.Contract.LinesTable._ID));
c.close();
}
values.remove(NextGenDB.Contract.LinesTable.COLUMN_NAME);
values.put(BranchesTable.COL_LINE,lineid);
last_rowid = db.insertWithOnConflict(NextGenDB.Contract.BranchesTable.TABLE_NAME,null,values,SQLiteDatabase.CONFLICT_REPLACE);
break;
case MANY_STOPS:
//Log.d("AppDataProvider_busTO","New stop insert request");
try{
last_rowid = db.insertOrThrow(NextGenDB.Contract.StopsTable.TABLE_NAME,null,values);
} catch (SQLiteConstraintException e){
Log.w("AppDataProvider_busTO","Insert failed because of constraint");
last_rowid = -1;
e.printStackTrace();
}
break;
case CONNECTIONS:
try{
last_rowid = db.insertOrThrow(NextGenDB.Contract.ConnectionsTable.TABLE_NAME,null,values);
} catch (SQLiteConstraintException e){
Log.w("AppDataProvider_busTO","Insert failed because of constraint");
last_rowid = -1;
e.printStackTrace();
}
break;
default:
throw new IllegalArgumentException("Invalid parameters");
}
finalUri = ContentUris.withAppendedId(uri,last_rowid);
return finalUri;
}
@Override
public boolean onCreate() {
+ con = getContext();
appDBHelper = new NextGenDB(getContext());
udbhelper = new UserDB(getContext());
+ if(con!=null) {
+ preferences = new GlobalStatusPreferences(con);
+ } else {
+ preferences = null;
+ Log.e(DEBUG_TAG,"Cannot get shared preferences");
+ }
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws UnsupportedOperationException,IllegalArgumentException{
+ //IMPORTANT
+ if(preferences.isDBUpdating()) return null;
SQLiteDatabase db = appDBHelper.getReadableDatabase();
List parts = uri.getPathSegments();
switch (sUriMatcher.match(uri)){
case LOCATION_SEARCH:
//authority/stops/location/"Lat"/"Lon"/"distance"
//distance in metres (integer)
if(parts.size()>=4 && "location".equals(parts.get(1))){
Double latitude = Double.parseDouble(parts.get(2));
Double longitude = Double.parseDouble(parts.get(3));
//converting distance to a float to not lose precision
Float distance = parts.size()>=5 ? Float.parseFloat(parts.get(4))/1000 : 0.1f;
if(parts.size()>=5)
Log.d("LocationSearch"," given distance to search is "+parts.get(4)+" m");
Double distasAngle = (distance/6371)*180/Math.PI; //small angles approximation, still valid for about 500 metres
String whereClause = StopsTable.COL_LAT+ "< "+(latitude+distasAngle)+" AND "
+StopsTable.COL_LAT +" > "+(latitude-distasAngle)+" AND "+
StopsTable.COL_LONG+" < "+(longitude+distasAngle)+" AND "+StopsTable.COL_LONG+" > "+(longitude-distasAngle);
//Log.d("Provider-LOCSearch","Querying stops by position, query args: \n"+whereClause);
return db.query(StopsTable.TABLE_NAME,projection,whereClause,null,null,null,null);
}
else {
Log.w(DEBUG_TAG,"Not enough parameters");
if(parts.size()>=5) for(String s:parts) Log.d(DEBUG_TAG,"\t element "+parts.indexOf(s)+" is: "+s);
return null;
}
case FAVORITES_OP:
final String stopFavSelection = UserDB.getFavoritesColumnNamesAsArray[0]+" = ?";
db = udbhelper.getReadableDatabase();
Log.d(DEBUG_TAG,"Asked information on Favorites about stop with id "+uri.getLastPathSegment());
return db.query(UserDB.TABLE_NAME,projection,stopFavSelection,new String[]{uri.getLastPathSegment()},null,null,sortOrder);
case STOP_OP:
//Let's try this plain and simple
final String[] selectionValues = {uri.getLastPathSegment()};
final String stopSelection = StopsTable.COL_ID+" = ?";
Log.d(DEBUG_TAG,"Asked information about stop with id "+selectionValues[0]);
return db.query(StopsTable.TABLE_NAME,projection,stopSelection,selectionValues,null,null,sortOrder);
default:
Log.d("DataProvider","got request "+uri.getPath()+" which doesn't match anything");
}
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
// public static Uri getBaseUriGivenOp(int operationType);
public static Uri.Builder getUriBuilderToComplete(){
final Uri.Builder b = new Uri.Builder();
b.scheme("content").authority(AUTHORITY);
return b;
}
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ }
}
diff --git a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java
index 459d41f..dc3a845 100644
--- a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java
+++ b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java
@@ -1,347 +1,354 @@
/*
BusTO (middleware)
Copyright (C) 2018 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.content.ContentResolver;
import android.content.ContentValues;
+import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.fragments.FragmentHelper;
import it.reyboz.bustorino.middleware.NextGenDB.Contract.*;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Calendar;
/**
* This should be used to download data, but not to display it
*/
public class AsyncDataDownload extends AsyncTask{
private static final String TAG = "BusTO-DataDownload";
private boolean failedAll = false;
private AtomicReference res;
private RequestType t;
private String query;
WeakReference helperRef;
private ArrayList otherActivities = new ArrayList<>();
public AsyncDataDownload(RequestType type,FragmentHelper fh) {
t = type;
helperRef = new WeakReference<>(fh);
res = new AtomicReference<>();
}
@Override
protected Object doInBackground(String... params) {
RecursionHelper r;
boolean success=false;
Object result;
switch (t){
case ARRIVALS:
r = new RecursionHelper<>(new ArrivalsFetcher[] {new FiveTAPIFetcher(),new GTTJSONFetcher(), new FiveTScraperFetcher()});
break;
case STOPS:
r = new RecursionHelper<>(new StopsFinderByName[] {new GTTStopsFetcher(), new FiveTStopsFetcher()});
break;
default:
//TODO put error message
return null;
}
FragmentHelper fh = helperRef.get();
//If the FragmentHelper is null, that means the activity doesn't exist anymore
if (fh == null){
return null;
}
//Log.d(TAG,"refresh layout reference is: "+fh.isRefreshLayoutReferenceTrue());
while(r.valid()) {
if(this.isCancelled()) {
return null;
}
//get the data from the fetcher
switch (t){
case ARRIVALS:
ArrivalsFetcher f = (ArrivalsFetcher) r.getAndMoveForward();
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
if(f instanceof FiveTAPIFetcher && Integer.parseInt(stopID)>= 8200)
continue;
p= f.ReadArrivalTimesAll(stopID,res);
publishProgress(res.get());
if(f instanceof FiveTAPIFetcher){
AtomicReference gres = new AtomicReference<>();
List branches = ((FiveTAPIFetcher) f).getDirectionsForStop(stopID,gres);
if(gres.get() == Fetcher.result.OK){
p.addInfoFromRoutes(branches);
Thread t = new Thread(new BranchInserter(branches,fh,stopID));
t.start();
otherActivities.add(t);
}
//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);
}
}
}
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
break;
default:
result = null;
}
//find if it went well
if(res.get()== Fetcher.result.OK) {
//wait for other threads to finish
for(Thread t: otherActivities){
try {
t.join();
} catch (InterruptedException e) {
//do nothing
}
}
return result;
}
}
//at this point, we are sure that the result has been negative
failedAll=true;
return null;
}
@Override
protected void onProgressUpdate(Fetcher.result... values) {
FragmentHelper fh = helperRef.get();
if (fh!=null)
for (Fetcher.result r : values){
//TODO: make Toast
fh.showErrorMessage(r);
}
else {
Log.w(TAG,"We had to show some progress but activity was destroyed");
}
}
@Override
protected void onPostExecute(Object o) {
FragmentHelper fh = helperRef.get();
if(failedAll || o == null || fh == null){
//everything went bad
if(fh!=null) fh.toggleSpinner(false);
cancel(true);
//TODO: send message here
return;
}
switch (t){
case ARRIVALS:
Palina palina = (Palina) o;
fh.createOrUpdateStopFragment(palina);
break;
case STOPS:
//this should never be a problem
List stopList = (List) o;
if(query!=null) {
fh.createFragmentFor(stopList,query);
} else Log.e(TAG,"QUERY NULL, COULD NOT CREATE FRAGMENT");
break;
case DBUPDATE:
break;
}
}
@Override
protected void onCancelled() {
FragmentHelper fh = helperRef.get();
if (fh!=null) fh.toggleSpinner(false);
}
@Override
protected void onPreExecute() {
FragmentHelper fh = helperRef.get();
if (fh!=null) fh.toggleSpinner(true);
}
public enum RequestType {
ARRIVALS,STOPS,DBUPDATE
}
/**
* Run this in a background thread.
* Sets a stop name for this.palina, guaranteed not to be null!
**/
//TODO:Implement this
/*
private void getNameOrGetRekt(Palina p) {
String nameMaybe;
SQLiteDatabase udb = uDB.getReadableDatabase();
// does it already have a name (for fetchers that support it, or already got from favorites)?
nameMaybe = p.getStopDisplayName();
if(nameMaybe != null && nameMaybe.length() > 0) {
return;
}
// ok, let's search favorites.
String usernameMaybe = UserDB.getStopUserName(udb, this.p.ID);
if(usernameMaybe != null && usernameMaybe.length() > 0) {
p.setStopUserName(usernameMaybe);
return;
}
// let's try StopsDB, then.
db.openIfNeeded();
nameMaybe = db.getNameFromID(this.p.ID);
db.closeIfNeeded();
if(nameMaybe != null && nameMaybe.length() > 0) {
p.setStopName(nameMaybe);
return;
}
// no name to be found anywhere, don't bother searching it next time
p.setStopName("");
}*/
public class BranchInserter implements Runnable{
private List routesToInsert;
private String stopID;
private FragmentHelper fragmentHelper;
public BranchInserter(List routesToInsert,FragmentHelper fh,String stopID) {
this.routesToInsert = routesToInsert;
this.stopID = stopID;
this.fragmentHelper = fh;
}
@Override
public void run() {
ContentValues[] values = new ContentValues[routesToInsert.size()];
ArrayList connectionsVals = new ArrayList<>(routesToInsert.size()*4);
long starttime,endtime;
for (Route r:routesToInsert){
//if it has received an interrupt, stop
if(Thread.interrupted()) return;
//otherwise, build contentValues
final ContentValues cv = new ContentValues();
cv.put(BranchesTable.COL_BRANCHID,r.branchid);
cv.put(LinesTable.COLUMN_NAME,r.name);
cv.put(BranchesTable.COL_DIRECTION,r.destinazione);
cv.put(BranchesTable.COL_DESCRIPTION,r.description);
for (int day :r.serviceDays) {
switch (day){
case Calendar.MONDAY:
cv.put(BranchesTable.COL_LUN,1);
break;
case Calendar.TUESDAY:
cv.put(BranchesTable.COL_MAR,1);
break;
case Calendar.WEDNESDAY:
cv.put(BranchesTable.COL_MER,1);
break;
case Calendar.THURSDAY:
cv.put(BranchesTable.COL_GIO,1);
break;
case Calendar.FRIDAY:
cv.put(BranchesTable.COL_VEN,1);
break;
case Calendar.SATURDAY:
cv.put(BranchesTable.COL_SAB,1);
break;
case Calendar.SUNDAY:
cv.put(BranchesTable.COL_DOM,1);
break;
}
}
if(r.type!=null) cv.put(BranchesTable.COL_TYPE, r.type.getCode());
cv.put(BranchesTable.COL_FESTIVO, r.festivo.getCode());
values[routesToInsert.indexOf(r)] = cv;
for(int i=0; i.
*/
package it.reyboz.bustorino.middleware;
import android.app.IntentService;
import android.content.*;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;
import static it.reyboz.bustorino.middleware.NextGenDB.Contract.*;
/**
* An {@link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
*/
public class DatabaseUpdateService extends IntentService {
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
private static final String ACTION_UPDATE = "it.reyboz.bustorino.middleware.action.UPDATE_DB";
private static final String DB_VERSION = "NextGenDB.GTTVersion";
private static final String DEBUG_TAG = "DatabaseService_BusTO";
// TODO: Rename parameters
private static final String TRIAL = "it.reyboz.bustorino.middleware.extra.TRIAL";
private static final int MAX_TRIALS = 5;
public DatabaseUpdateService() {
super("DatabaseUpdateService");
}
-
+ private boolean isRunning;
private int updateTrial;
/**
* Starts this service to perform action Foo with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
public static void startDBUpdate(Context context) {
startDBUpdate(context,0);
}
public static void startDBUpdate(Context con, int trial){
Intent intent = new Intent(con, DatabaseUpdateService.class);
intent.setAction(ACTION_UPDATE);
intent.putExtra(TRIAL,trial);
con.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_UPDATE.equals(action)) {
Log.d(DEBUG_TAG,"Started action update");
SharedPreferences shPr = getSharedPreferences(getString(R.string.mainSharedPreferences),MODE_PRIVATE);
int versionDB = shPr.getInt(DB_VERSION,-1);
final int trial = intent.getIntExtra(TRIAL,-1);
updateTrial = trial;
int newVersion = getNewVersion(trial);
Log.d(DEBUG_TAG,"newDBVersion: "+newVersion+" oldVersion: "+versionDB);
if(versionDB==-1 || newVersion>versionDB){
final SharedPreferences.Editor editor = shPr.edit();
editor.putBoolean(getString(R.string.databaseUpdatingPref),true);
editor.apply();
Log.d(DEBUG_TAG,"Downloading the bus stops info");
final AtomicReference gres = new AtomicReference<>();
if(!performDBUpdate(gres)) restartDBUpdateifPossible(trial,gres);
/*switch (gres.get()){
case SERVER_ERROR:
restartDBUpdateifPossible(trial);
break;
case PARSER_ERROR:
break;
case EMPTY_RESULT_SET:
break;
case QUERY_TOO_SHORT:
break;
case SERVER_ERROR_404:
break;
}*/
else {
editor.putInt(DB_VERSION,newVersion);
// BY COMMENTING THIS, THE APP WILL CONTINUOUSLY UPDATE THE DATABASE
editor.apply();
}
} else {
Log.d(DEBUG_TAG,"No update needed");
}
Log.d(DEBUG_TAG,"Finished update");
SharedPreferences.Editor editor = shPr.edit();
editor.putBoolean(getString(R.string.databaseUpdatingPref),false);
editor.apply();
}
}
}
private boolean performDBUpdate(AtomicReference gres){
final FiveTAPIFetcher f = new FiveTAPIFetcher();
final ArrayList stops = f.getAllStopsFromGTT(gres);
//final ArrayList cpOp = new ArrayList<>();
if(gres.get()!= Fetcher.result.OK){
Log.w(DEBUG_TAG,"Something went wrong downloading");
return false;
}
final NextGenDB dbHelp = new NextGenDB(getApplicationContext());
final SQLiteDatabase db = dbHelp.getWritableDatabase();
//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 "+stops.size()+" stops");
for (final Stop s : stops) {
final ContentValues cv = new ContentValues();
cv.put(StopsTable.COL_ID, s.ID);
cv.put(StopsTable.COL_NAME, s.getStopDefaultName());
if (s.location != null)
cv.put(StopsTable.COL_LOCATION, s.location);
cv.put(StopsTable.COL_LAT, s.getLatitude());
cv.put(StopsTable.COL_LONG, s.getLongitude());
if (s.getAbsurdGTTPlaceName() != null) cv.put(StopsTable.COL_PLACE, s.getAbsurdGTTPlaceName());
cv.put(StopsTable.COL_LINES_STOPPING, s.routesThatStopHereToString());
if (s.type != null) cv.put(StopsTable.COL_TYPE, s.type.getCode());
//Log.d(DEBUG_TAG,cv.toString());
//cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
//valuesArr[i] = cv;
db.replace(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");
final ArrayList routes = f.getAllLinesFromGTT(gres);
if(routes==null){
Log.w(DEBUG_TAG,"Something went wrong downloading the lines");
+ dbHelp.close();
return false;
}
db.beginTransaction();
startTime = System.currentTimeMillis();
for (Route r: routes){
final ContentValues cv = new ContentValues();
cv.put(LinesTable.COLUMN_NAME,r.name);
switch (r.type){
case BUS:
cv.put(LinesTable.COLUMN_TYPE,"URBANO");
break;
case RAILWAY:
cv.put(LinesTable.COLUMN_TYPE,"FERROVIA");
break;
case LONG_DISTANCE_BUS:
cv.put(LinesTable.COLUMN_TYPE,"EXTRA");
break;
}
cv.put(LinesTable.COLUMN_DESCRIPTION,r.description);
//db.insert(LinesTable.TABLE_NAME,null,cv);
int rows = db.update(LinesTable.TABLE_NAME,cv,LinesTable.COLUMN_NAME+" = ?",new String[]{r.name});
if(rows<1){ //we haven't changed anything
db.insert(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 true;
}
private int getNewVersion(int trial){
AtomicReference gres = new AtomicReference<>();
String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
if(networkRequest == null){
restartDBUpdateifPossible(trial,gres);
return -2;
}
boolean needed;
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 -2;
}
}
private void restartDBUpdateifPossible(int currentTrial,AtomicReference res){
if (currentTrial