diff --git a/src/it/reyboz/bustorino/fragments/FragmentHelper.java b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
index 88fc966..4c68de6 100644
--- a/src/it/reyboz/bustorino/fragments/FragmentHelper.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
@@ -1,225 +1,225 @@
/*
BusTO (fragments)
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.fragments;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.sqlite.SQLiteException;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.adapters.PalinaAdapter;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.middleware.*;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* Helper class to manage the fragments and their needs
*/
public class FragmentHelper {
GeneralActivity act;
private Stop lastSuccessfullySearchedBusStop;
//support for multiple frames
private int primaryFrameLayout,secondaryFrameLayout, swipeRefID;
public static final int NO_FRAME = -3;
private WeakReference lastTaskRef;
private NextGenDB newDBHelper;
private boolean shouldHaltAllActivities=false;
public FragmentHelper(GeneralActivity act, int swipeRefID, int mainFrame) {
this(act,swipeRefID,mainFrame,NO_FRAME);
}
public FragmentHelper(GeneralActivity act, int swipeRefID, int primaryFrameLayout, int secondaryFrameLayout) {
this.act = act;
this.swipeRefID = swipeRefID;
this.primaryFrameLayout = primaryFrameLayout;
this.secondaryFrameLayout = secondaryFrameLayout;
- newDBHelper = new NextGenDB(act.getApplicationContext());
+ newDBHelper = NextGenDB.getInstance(act.getApplicationContext());
}
public Stop getLastSuccessfullySearchedBusStop() {
return lastSuccessfullySearchedBusStop;
}
public void setLastSuccessfullySearchedBusStop(Stop stop) {
this.lastSuccessfullySearchedBusStop = stop;
}
public void setLastTaskRef(WeakReference lastTaskRef) {
this.lastTaskRef = lastTaskRef;
}
/**
* Called when you need to create a fragment for a specified Palina
* @param p the Stop that needs to be displayed
*/
public void createOrUpdateStopFragment(Palina p){
boolean sameFragment;
ArrivalsFragment arrivalsFragment;
if(act==null || shouldHaltAllActivities) {
//SOMETHING WENT VERY WRONG
return;
}
SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID);
FragmentManager fm = act.getSupportFragmentManager();
if(fm.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(R.id.resultFrame);
sameFragment = arrivalsFragment.isFragmentForTheSameStop(p);
} else
sameFragment = false;
setLastSuccessfullySearchedBusStop(p);
if(!sameFragment) {
//set the String to be displayed on the fragment
String displayName = p.getStopDisplayName();
String displayStuff;
if (displayName != null && displayName.length() > 0) {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName);
} else {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID);
}
attachFragmentToContainer(fm,arrivalsFragment,true,ResultListFragment.getFragmentTag(p));
} else {
Log.d("BusTO", "Same bus stop, accessing existing fragment");
arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(R.id.resultFrame);
}
arrivalsFragment.setListAdapter(new PalinaAdapter(act.getApplicationContext(),p));
act.hideKeyboard();
toggleSpinner(false);
}
/**
* Called when you need to display the results of a search of stops
* @param resultList the List of stops found
* @param query String queried
*/
public void createFragmentFor(List resultList,String query){
act.hideKeyboard();
StopListFragment listfragment = StopListFragment.newInstance(query);
attachFragmentToContainer(act.getSupportFragmentManager(),listfragment,false,"search_"+query);
listfragment.setStopList(resultList);
toggleSpinner(false);
}
/**
* Wrapper for toggleSpinner in Activity
* @param on new status of spinner system
*/
public void toggleSpinner(boolean on){
if (act instanceof FragmentListener)
((FragmentListener) act).toggleSpinner(on);
else {
SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID);
srl.setRefreshing(false);
}
}
/**
* Attach a new fragment to a cointainer
* @param fm the FragmentManager
* @param fragment the Fragment
* @param sendToSecondaryFrame needs to be displayed in secondary frame or not
* @param tag tag for the fragment
*/
public void attachFragmentToContainer(FragmentManager fm,Fragment fragment, boolean sendToSecondaryFrame, String tag){
FragmentTransaction ft = fm.beginTransaction();
if(sendToSecondaryFrame && secondaryFrameLayout!=NO_FRAME)
ft.replace(secondaryFrameLayout,fragment,tag);
else ft.replace(primaryFrameLayout,fragment,tag);
ft.addToBackStack("state_"+tag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
ft.commit();
//fm.executePendingTransactions();
}
synchronized public int insertBatchDataInNextGenDB(ContentValues[] valuesArr,String tableName){
if(newDBHelper !=null)
try {
return newDBHelper.insertBatchContent(valuesArr, tableName);
} catch (SQLiteException exc){
Log.w("DB Batch inserting: ","ERROR Inserting the data batch: ",exc.fillInStackTrace());
return -2;
}
else return -1;
}
synchronized public ContentResolver getContentResolver(){
return act.getContentResolver();
}
public void setBlockAllActivities(boolean shouldI) {
this.shouldHaltAllActivities = shouldI;
}
public void stopLastRequestIfNeeded(){
if(lastTaskRef == null) return;
AsyncDataDownload task = lastTaskRef.get();
if(task!=null){
task.cancel(true);
}
}
/**
* Wrapper to show the errors/status that happened
* @param res result from Fetcher
*/
public void showErrorMessage(Fetcher.result res){
//TODO: implement a common set of errors for all fragments
switch (res){
case OK:
break;
case CLIENT_OFFLINE:
act.showMessage(R.string.network_error);
break;
case SERVER_ERROR:
if (act.isConnected()) {
act.showMessage(R.string.parsing_error);
} else {
act.showMessage(R.string.network_error);
}
case PARSER_ERROR:
default:
act.showMessage(R.string.internal_error);
break;
case QUERY_TOO_SHORT:
act.showMessage(R.string.query_too_short);
break;
case EMPTY_RESULT_SET:
act.showMessage(R.string.no_bus_stop_have_this_name);
break;
}
}
}
diff --git a/src/it/reyboz/bustorino/middleware/AppDataProvider.java b/src/it/reyboz/bustorino/middleware/AppDataProvider.java
index c0362b5..c2460f9 100644
--- a/src/it/reyboz/bustorino/middleware/AppDataProvider.java
+++ b/src/it/reyboz/bustorino/middleware/AppDataProvider.java
@@ -1,268 +1,268 @@
/*
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.*;
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.backend.DBStatusManager;
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 DBStatusManager 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(true))
return null;
db = appDBHelper.getWritableDatabase();
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());
+ appDBHelper = NextGenDB.getInstance(getContext());
udbhelper = new UserDB(getContext());
if(con!=null) {
preferences = new DBStatusManager(con,null);
} 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
//The app should not query when the DB is updating, but apparently, it does
if(preferences.isDBUpdating(true))
//throw new UnsupportedOperationException("DB is updating");
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/DatabaseUpdateService.java b/src/it/reyboz/bustorino/middleware/DatabaseUpdateService.java
index 5492b47..99f8963 100644
--- a/src/it/reyboz/bustorino/middleware/DatabaseUpdateService.java
+++ b/src/it/reyboz/bustorino/middleware/DatabaseUpdateService.java
@@ -1,278 +1,278 @@
/*
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.app.IntentService;
import android.content.*;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.Nullable;
import android.util.Log;
import it.reyboz.bustorino.ActivityMain;
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.lang.ref.WeakReference;
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 String COMPULSORY = "compulsory_update";
private static final int MAX_TRIALS = 5;
private static final int VERSION_UNAIVALABLE = -2;
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 con, int trial, @Nullable Boolean mustUpdate){
Intent intent = new Intent(con, DatabaseUpdateService.class);
intent.setAction(ACTION_UPDATE);
intent.putExtra(TRIAL,trial);
if(mustUpdate!=null){
intent.putExtra(COMPULSORY,mustUpdate);
}
con.startService(intent);
}
public static void startDBUpdate(Context con) {
startDBUpdate(con, 0, false);
}
@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);
final SharedPreferences.Editor editor = shPr.edit();
updateTrial = trial;
UpdateRequestParams params = new UpdateRequestParams(intent);
int newVersion = getNewVersion(params);
if(newVersion==VERSION_UNAIVALABLE){
//NOTHING LEFT TO DO
return;
}
Log.d(DEBUG_TAG,"newDBVersion: "+newVersion+" oldVersion: "+versionDB);
if(params.mustUpdate || versionDB==-1 || newVersion>versionDB){
Log.d(DEBUG_TAG,"Downloading the bus stops info");
final AtomicReference gres = new AtomicReference<>();
if(!performDBUpdate(gres))
restartDBUpdateifPossible(params,gres);
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");
setDBUpdatingFlag(shPr,false);
}
}
}
private boolean setDBUpdatingFlag(SharedPreferences shPr,boolean value){
final SharedPreferences.Editor editor = shPr.edit();
editor.putBoolean(getString(R.string.databaseUpdatingPref),value);
return editor.commit();
}
private boolean setDBUpdatingFlag(boolean value){
final SharedPreferences shPr = getSharedPreferences(getString(R.string.mainSharedPreferences),MODE_PRIVATE);
return setDBUpdatingFlag(shPr,value);
}
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;
}
if(!setDBUpdatingFlag(true))
return false; //If the commit to the SharedPreferences didn't succeed, simply stop updating the database
- final NextGenDB dbHelp = new NextGenDB(getApplicationContext());
+ final NextGenDB dbHelp = NextGenDB.getInstance(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.getName());
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.getName()});
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(UpdateRequestParams params){
AtomicReference gres = new AtomicReference<>();
String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
if(networkRequest == null){
restartDBUpdateifPossible(params,gres);
return VERSION_UNAIVALABLE;
}
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 -4;
}
}
private void restartDBUpdateifPossible(UpdateRequestParams pars, AtomicReference res){
if (pars.trial.
*/
package it.reyboz.bustorino.middleware;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import android.util.Log;
import it.reyboz.bustorino.R;
import static it.reyboz.bustorino.middleware.NextGenDB.Contract.*;
public class NextGenDB extends SQLiteOpenHelper{
public static final String DATABASE_NAME = "bustodatabase.db";
public static final int DATABASE_VERSION = 2;
+ //Singleton instance
+ private static volatile NextGenDB instance = null;
//Some generating Strings
private static final String SQL_CREATE_LINES_TABLE="CREATE TABLE "+Contract.LinesTable.TABLE_NAME+" ("+
Contract.LinesTable._ID +" INTEGER PRIMARY KEY AUTOINCREMENT, "+ Contract.LinesTable.COLUMN_NAME +" TEXT, "+
Contract.LinesTable.COLUMN_DESCRIPTION +" TEXT, "+Contract.LinesTable.COLUMN_TYPE +" TEXT, "+
"UNIQUE ("+LinesTable.COLUMN_NAME+","+LinesTable.COLUMN_DESCRIPTION+","+LinesTable.COLUMN_TYPE+" ) "+" )";
private static final String SQL_CREATE_BRANCH_TABLE="CREATE TABLE "+Contract.BranchesTable.TABLE_NAME+" ("+
Contract.BranchesTable._ID +" INTEGER, "+ Contract.BranchesTable.COL_BRANCHID +" INTEGER PRIMARY KEY, "+
Contract.BranchesTable.COL_LINE +" INTEGER, "+ Contract.BranchesTable.COL_DESCRIPTION +" TEXT, "+
Contract.BranchesTable.COL_DIRECTION+" TEXT, "+ Contract.BranchesTable.COL_TYPE +" INTEGER, "+
//SERVICE DAYS: 0 => FERIALE,1=>FESTIVO,-1=>UNKNOWN,add others if necessary
Contract.BranchesTable.COL_FESTIVO +" INTEGER, "+
//DAYS COLUMNS. IT'S SO TEDIOUS I TRIED TO KILL MYSELF
BranchesTable.COL_LUN+" INTEGER, "+BranchesTable.COL_MAR+" INTEGER, "+BranchesTable.COL_MER+" INTEGER, "+BranchesTable.COL_GIO+" INTEGER, "+
BranchesTable.COL_VEN+" INTEGER, "+ BranchesTable.COL_SAB+" INTEGER, "+BranchesTable.COL_DOM+" INTEGER, "+
"FOREIGN KEY("+ Contract.BranchesTable.COL_LINE +") references "+ Contract.LinesTable.TABLE_NAME+"("+ Contract.LinesTable._ID+") "
+")";
private static final String SQL_CREATE_CONNECTIONS_TABLE="CREATE TABLE "+Contract.ConnectionsTable.TABLE_NAME+" ("+
Contract.ConnectionsTable.COLUMN_BRANCH+" INTEGER, "+ Contract.ConnectionsTable.COLUMN_STOP_ID+" TEXT, "+
Contract.ConnectionsTable.COLUMN_ORDER+" INTEGER, "+
"PRIMARY KEY ("+ Contract.ConnectionsTable.COLUMN_BRANCH+","+ Contract.ConnectionsTable.COLUMN_STOP_ID + "), "+
"FOREIGN KEY("+ Contract.ConnectionsTable.COLUMN_BRANCH+") references "+ Contract.BranchesTable.TABLE_NAME+"("+ Contract.BranchesTable.COL_BRANCHID +"), "+
"FOREIGN KEY("+ Contract.ConnectionsTable.COLUMN_STOP_ID+") references "+ Contract.StopsTable.TABLE_NAME+"("+ Contract.StopsTable.COL_ID +") "
+")";
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, "+
Contract.StopsTable.COL_LOCATION+" TEXT, "+Contract.StopsTable.COL_PLACE+" TEXT, "+
Contract.StopsTable.COL_LINES_STOPPING +" TEXT )";
private static final String SQL_CREATE_STOPS_TABLE_TO_COMPLETE = " ("+
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, "+
Contract.StopsTable.COL_LOCATION+" TEXT, "+Contract.StopsTable.COL_PLACE+" TEXT, "+
Contract.StopsTable.COL_LINES_STOPPING +" TEXT )";
private Context appContext;
- public NextGenDB(Context context) {
+ private NextGenDB(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
appContext = context.getApplicationContext();
}
+
+ /**
+ * Lazy initialization singleton getter, thread-safe with double checked locking
+ * from https://en.wikipedia.org/wiki/Singleton_pattern
+ * @param context needed context
+ * @return the instance
+ */
+ public static NextGenDB getInstance(Context context){
+ if(instance==null){
+ synchronized (NextGenDB.class){
+ if(instance==null){
+ instance = new NextGenDB(context);
+ }
+ }
+ }
+ return instance;
+ }
+
@Override
public void onCreate(SQLiteDatabase db) {
Log.d("BusTO-AppDB","Lines creating database:\n"+SQL_CREATE_LINES_TABLE+"\n"+
SQL_CREATE_STOPS_TABLE+"\n"+SQL_CREATE_BRANCH_TABLE+"\n"+SQL_CREATE_CONNECTIONS_TABLE);
db.execSQL(SQL_CREATE_LINES_TABLE);
db.execSQL(SQL_CREATE_STOPS_TABLE);
//tables with constraints
db.execSQL(SQL_CREATE_BRANCH_TABLE);
db.execSQL(SQL_CREATE_CONNECTIONS_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(oldVersion<2 && newVersion == 2){
//DROP ALL TABLES
db.execSQL("DROP TABLE "+ConnectionsTable.TABLE_NAME);
db.execSQL("DROP TABLE "+BranchesTable.TABLE_NAME);
db.execSQL("DROP TABLE "+LinesTable.TABLE_NAME);
db.execSQL("DROP TABLE "+ StopsTable.TABLE_NAME);
//RECREATE THE TABLES WITH THE NEW SCHEMA
db.execSQL(SQL_CREATE_LINES_TABLE);
db.execSQL(SQL_CREATE_STOPS_TABLE);
//tables with constraints
db.execSQL(SQL_CREATE_BRANCH_TABLE);
db.execSQL(SQL_CREATE_CONNECTIONS_TABLE);
DatabaseUpdateService.startDBUpdate(appContext,0,true);
}
}
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.execSQL("PRAGMA foreign_keys=ON");
}
public static String getSqlCreateStopsTable(String tableName){
return "CREATE TABLE "+tableName+" ("+
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, "+
Contract.StopsTable.COL_LOCATION+" TEXT, "+Contract.StopsTable.COL_PLACE+" TEXT, "+
Contract.StopsTable.COL_LINES_STOPPING +" TEXT )";
}
/**
* Insert batch content, already prepared as
* @param content ContentValues array
* @return number of lines inserted
*/
public int insertBatchContent(ContentValues[] content,String tableName) throws SQLiteException {
final SQLiteDatabase db = this.getWritableDatabase();
int success = 0;
db.beginTransaction();
for (final ContentValues cv : content) {
try {
db.replaceOrThrow(tableName, null, cv);
success++;
} catch (SQLiteConstraintException d){
Log.w("NextGenDB_Insert","Failed insert with FOREIGN KEY... \n"+d.getMessage());
} catch (Exception e) {
Log.w("NextGenDB_Insert", e);
}
}
db.setTransactionSuccessful();
db.endTransaction();
return success;
}
public static final class Contract{
//Ok, I get it, it really is a pain in the ass..
// But it's the only way to have maintainable code
public interface DataTables {
String getTableName();
String[] getFields();
}
public static final class LinesTable implements BaseColumns, DataTables {
//The fields
public static final String TABLE_NAME = "lines";
public static final String COLUMN_NAME = "line_name";
public static final String COLUMN_DESCRIPTION = "line_description";
public static final String COLUMN_TYPE = "line_bacino";
@Override
public String getTableName() {
return TABLE_NAME;
}
@Override
public String[] getFields() {
return new String[]{COLUMN_NAME,COLUMN_DESCRIPTION,COLUMN_TYPE};
}
}
public static final class BranchesTable implements BaseColumns, DataTables {
public static final String TABLE_NAME = "branches";
public static final String COL_BRANCHID = "branchid";
public static final String COL_LINE = "lineid";
public static final String COL_DESCRIPTION = "branch_description";
public static final String COL_DIRECTION = "branch_direzione";
public static final String COL_FESTIVO = "branch_festivo";
public static final String COL_TYPE = "branch_type";
public static final String COL_LUN="runs_lun";
public static final String COL_MAR="runs_mar";
public static final String COL_MER="runs_mer";
public static final String COL_GIO="runs_gio";
public static final String COL_VEN="runs_ven";
public static final String COL_SAB="runs_sab";
public static final String COL_DOM="runs_dom";
@Override
public String getTableName() {
return TABLE_NAME;
}
@Override
public String[] getFields() {
return new String[]{COL_BRANCHID,COL_LINE,COL_DESCRIPTION,
COL_DIRECTION,COL_FESTIVO,COL_TYPE,
COL_LUN,COL_MAR,COL_MER,COL_GIO,COL_VEN,COL_SAB,COL_DOM
};
}
}
public static final class ConnectionsTable implements DataTables {
public static final String TABLE_NAME = "connections";
public static final String COLUMN_BRANCH = "branchid";
public static final String COLUMN_STOP_ID = "stopid";
public static final String COLUMN_ORDER = "ordine";
@Override
public String getTableName() {
return TABLE_NAME;
}
@Override
public String[] getFields() {
return new String[]{COLUMN_STOP_ID,COLUMN_BRANCH,COLUMN_ORDER};
}
}
public static final class StopsTable implements DataTables {
public static final String TABLE_NAME = "stops";
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_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};
}
}
}
public static final class DBUpdatingException extends Exception{
public DBUpdatingException(String message) {
super(message);
}
}
}