Sync.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. package com.ivalentin.margolariak;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.net.HttpURLConnection;
  6. import java.net.MalformedURLException;
  7. import java.net.URL;
  8. import java.net.URLEncoder;
  9. import java.util.ArrayList;
  10. import java.util.Iterator;
  11. import java.util.List;
  12. import java.util.regex.Pattern;
  13. import android.app.Activity;
  14. import android.app.Dialog;
  15. import android.content.Context;
  16. import android.content.SharedPreferences;
  17. import android.database.Cursor;
  18. import android.database.DatabaseUtils;
  19. import android.database.sqlite.SQLiteDatabase;
  20. import android.os.AsyncTask;
  21. import android.util.Log;
  22. import android.view.View;
  23. import android.view.View.OnClickListener;
  24. import android.view.Window;
  25. import android.widget.Button;
  26. import android.widget.ImageView;
  27. import android.widget.ProgressBar;
  28. import android.widget.TextView;
  29. import org.json.*;
  30. /**
  31. * AsyncTask that synchronizes the online database to the device.
  32. * Is run every time the app is started, and periodically in the background.
  33. *
  34. * @author Inigo Valentin
  35. *
  36. */
  37. class Sync extends AsyncTask<Void, Void, Void> {
  38. private final Context myContextRef;
  39. private ProgressBar pbSync;
  40. private ImageView ivSync;
  41. private Dialog dialog;
  42. private MainActivity activity;
  43. private int fg;
  44. private String strings[];
  45. private boolean doProgress = false;
  46. private long millis = 0;
  47. private TextView tv;
  48. /**
  49. * Things to do before sync. Namely, displaying a spinning progress bar.
  50. *
  51. * @see android.os.AsyncTask#onPreExecute()
  52. */
  53. @Override
  54. protected void onPreExecute(){
  55. //If pbSync and dialog are not null, it means that the sync is being done on the foreground
  56. if (pbSync != null) {
  57. //Show the spinning bar
  58. pbSync.setVisibility(View.VISIBLE);
  59. ivSync.setVisibility(View.GONE);
  60. }
  61. if (dialog != null) {
  62. //Show the dialog and assign the elements on it
  63. dialog.show();
  64. strings = new String[10];
  65. strings[0] = myContextRef.getString(R.string.dialog_sync_text_0);
  66. strings[1] = myContextRef.getString(R.string.dialog_sync_text_1);
  67. strings[2] = myContextRef.getString(R.string.dialog_sync_text_2);
  68. strings[3] = myContextRef.getString(R.string.dialog_sync_text_3);
  69. strings[4] = myContextRef.getString(R.string.dialog_sync_text_4);
  70. strings[5] = myContextRef.getString(R.string.dialog_sync_text_5);
  71. strings[6] = myContextRef.getString(R.string.dialog_sync_text_6);
  72. strings[7] = myContextRef.getString(R.string.dialog_sync_text_7);
  73. strings[8] = myContextRef.getString(R.string.dialog_sync_text_8);
  74. strings[9] = myContextRef.getString(R.string.dialog_sync_text_8); //In case I get a 9;
  75. tv = (TextView) dialog.findViewById(R.id.tv_dialog_sync_text);
  76. int idx = (int) (Math.random() * 9);
  77. tv.setText(strings[idx]);
  78. doProgress = true;
  79. }
  80. Log.d("Sync", "Starting full sync");
  81. }
  82. /**
  83. * Things to do after sync. Namely, hiding the spinning progress bar.
  84. *
  85. * @see android.os.AsyncTask#onPreExecute()
  86. */
  87. @Override
  88. protected void onPostExecute(Void v){
  89. //If pbSync and dialog are not null, it means that the sync is being done on the foreground
  90. if (pbSync != null) {
  91. //Hide the spinner
  92. pbSync.setVisibility(View.GONE);
  93. ivSync.setVisibility(View.VISIBLE);
  94. }
  95. if (dialog != null){
  96. //Close the dialog
  97. dialog.dismiss();
  98. //Check db version again
  99. SQLiteDatabase db = myContextRef.openOrCreateDatabase(GM.DB.NAME, Activity.MODE_PRIVATE, null);
  100. if (db.isReadOnly()){
  101. Log.e("Db ro", "Database is locked and in read only mode. Skipping sync.");
  102. return;
  103. }
  104. //Get database version
  105. Cursor cursor;
  106. int dbVersion;
  107. cursor = db.rawQuery("SELECT sum(version) AS v FROM version;", null);
  108. cursor.moveToFirst();
  109. dbVersion = cursor.getInt(0);
  110. cursor.close();
  111. db.close();
  112. //If the database is on it's initial version (i.e: There is no data, new or old)
  113. if (true){ //TODO dbVersion == GM.DATA.DEFAULT.DEFAULT_PREF_DB_VERSION){
  114. Log.e("SYNC", "Full sync failed");
  115. //Create a message dialog
  116. final Dialog dial = new Dialog(activity);
  117. dial.setCancelable(false);
  118. //Set up the window
  119. dial.requestWindowFeature(Window.FEATURE_NO_TITLE);
  120. dial.setContentView(R.layout.dialog_sync_failed);
  121. //Set button
  122. Button btClose = (Button) dial.findViewById(R.id.bt_dialog_sync_failed_close);
  123. btClose.setOnClickListener(new OnClickListener(){
  124. @Override
  125. public void onClick(View v) {
  126. dial.dismiss();
  127. activity.finish();
  128. }
  129. });
  130. //Show the dialog
  131. dial.show();
  132. }
  133. else{
  134. Log.d("SYNC", "Full sync finished");
  135. activity.loadSection(GM.SECTION.HOME);
  136. }
  137. }
  138. }
  139. /**
  140. * Called when the AsyncTask is created.
  141. * This constructor is intended for syncs on the background.
  142. *
  143. * @param myContextRef The Context of the calling activity.
  144. */
  145. public Sync(Activity myContextRef){
  146. this.myContextRef = myContextRef;
  147. }
  148. /**
  149. * Called when the AsyncTask is created.
  150. * This constructor is intended for syncs done from the foreground,
  151. * because it shows and hides a spinner (except for the initial one).
  152. *
  153. * @param myContextRef The Context of the calling activity.
  154. * @param pb The progress bar that will be shown while the sync goes on.
  155. */
  156. public Sync(Activity myContextRef, ProgressBar pb, ImageView iv) {
  157. this.myContextRef = myContextRef;
  158. dialog = null;
  159. pbSync = pb;
  160. ivSync = iv;
  161. fg = 1;
  162. }
  163. /**
  164. * Called when the AsyncTask is created.
  165. * This constructor is intended to use only in the first sync,
  166. * from the foreground, because a dialog will block the UI.
  167. *
  168. * @param myContextRef The Context of the calling activity.
  169. * @param d Dialog of the initial sync
  170. * @param pb The progress bar that will be shown while the sync goes on.
  171. * @param activity The calling MainActvity
  172. */
  173. public Sync(Activity myContextRef, ProgressBar pb, ImageView iv, Dialog d, MainActivity activity) {
  174. this.dialog = d;
  175. this.activity = activity;
  176. this.myContextRef = myContextRef;
  177. pbSync = pb;
  178. ivSync = iv;
  179. fg = 1;
  180. }
  181. /**
  182. * Called when the AsyncTask is created.
  183. *
  184. * @param context The Context of the calling activity.
  185. */
  186. public Sync(Context context) {
  187. this.myContextRef = context;
  188. pbSync = null;
  189. fg = 0;
  190. }
  191. @Override
  192. /**
  193. * Called when the AsyncTask is updated.
  194. * Used to change text in the sync window.
  195. */
  196. protected void onProgressUpdate(Void...progress) {
  197. if (doProgress){
  198. if (millis + 600 < System.currentTimeMillis()) {
  199. millis = System.currentTimeMillis();
  200. int idx = (int) (Math.random() * ((4) + 1));
  201. tv.setText(strings[idx]);
  202. }
  203. }
  204. }
  205. /**
  206. * Creates the URL required to performa a sync. Uses static data and data passed as arguments.
  207. *
  208. * @param user A unique user identifier.
  209. * @param version The db version of the client.
  210. * @param foreground 1 if the sync is done while the app is running, 0 otherwise.
  211. * @param lang Two letter language identifier.
  212. *
  213. * @return The URL that will be used for syncing.
  214. */
  215. private String buildUrl(String user, int version, int foreground, String lang){
  216. String url = "";
  217. try {
  218. url = GM.API.SERVER + GM.API.SYNC.PATH + "?" +
  219. GM.API.SYNC.KEY.CLIENT + "=" + URLEncoder.encode(GM.API.CLIENT, "UTF-8") + "&" +
  220. GM.API.SYNC.KEY.USER + "=" + URLEncoder.encode(user, "UTF-8") + "&" +
  221. GM.API.SYNC.KEY.ACTION + "=" + URLEncoder.encode(GM.API.SYNC.VALUE.ACTION, "UTF-8") + "&" +
  222. GM.API.SYNC.KEY.SECTION + "=" + URLEncoder.encode(GM.API.SYNC.VALUE.SECTION, "UTF-8") + "&" +
  223. GM.API.SYNC.KEY.VERSION + "=" + version + "&" +
  224. GM.API.SYNC.KEY.FOREGROUND + "=" + foreground + "&" +
  225. GM.API.SYNC.KEY.FORMAT + "=" + URLEncoder.encode(GM.API.SYNC.VALUE.FORMAT, "UTF-8") + "&" +
  226. GM.API.SYNC.KEY.LANG + "=" + URLEncoder.encode(lang, "UTF-8");
  227. }
  228. catch (java.io.UnsupportedEncodingException ex){
  229. Log.e("UTF-8", "Error encoding url for sync \"" + url + "\" - " + ex.toString());
  230. }
  231. return url;
  232. }
  233. /**
  234. * Recreates the database, dropping and then creating all the tables containing
  235. * data. "location" and "notification" tables are untouched.
  236. *
  237. * @param db A database connection.
  238. *
  239. * @return False if there were errors, true otherwise.
  240. */
  241. private boolean recreateDb(SQLiteDatabase db) {
  242. boolean result = true;
  243. try {
  244. db.execSQL(GM.DB.QUERY.DROP.ACTIVITY);
  245. db.execSQL(GM.DB.QUERY.DROP.ACTIVITY_COMMENT);
  246. db.execSQL(GM.DB.QUERY.DROP.ACTIVITY_IMAGE);
  247. db.execSQL(GM.DB.QUERY.DROP.ACTIVITY_ITINERARY);
  248. db.execSQL(GM.DB.QUERY.DROP.ACTIVITY_TAG);
  249. db.execSQL(GM.DB.QUERY.DROP.ALBUM);
  250. db.execSQL(GM.DB.QUERY.DROP.FESTIVAL);
  251. db.execSQL(GM.DB.QUERY.DROP.FESTIVAL_DAY);
  252. db.execSQL(GM.DB.QUERY.DROP.FESTIVAL_EVENT);
  253. db.execSQL(GM.DB.QUERY.DROP.FESTIVAL_EVENT_IMAGE);
  254. db.execSQL(GM.DB.QUERY.DROP.FESTIVAL_OFFER);
  255. db.execSQL(GM.DB.QUERY.DROP.PEOPLE);
  256. db.execSQL(GM.DB.QUERY.DROP.PHOTO);
  257. db.execSQL(GM.DB.QUERY.DROP.PHOTO_ALBUM);
  258. db.execSQL(GM.DB.QUERY.DROP.PHOTO_COMMENT);
  259. db.execSQL(GM.DB.QUERY.DROP.PLACE);
  260. db.execSQL(GM.DB.QUERY.DROP.POST);
  261. db.execSQL(GM.DB.QUERY.DROP.POST_COMMENT);
  262. db.execSQL(GM.DB.QUERY.DROP.POST_IMAGE);
  263. db.execSQL(GM.DB.QUERY.DROP.POST_TAG);
  264. db.execSQL(GM.DB.QUERY.DROP.SETTINGS);
  265. db.execSQL(GM.DB.QUERY.DROP.SPONSOR);
  266. db.execSQL(GM.DB.QUERY.DROP.VERSION);
  267. //TODO: Is this really necessary?
  268. Thread.sleep(1000);
  269. db.execSQL(GM.DB.QUERY.CREATE.ACTIVITY);
  270. db.execSQL(GM.DB.QUERY.CREATE.ACTIVITY_COMMENT);
  271. db.execSQL(GM.DB.QUERY.CREATE.ACTIVITY_IMAGE);
  272. db.execSQL(GM.DB.QUERY.CREATE.ACTIVITY_ITINERARY);
  273. db.execSQL(GM.DB.QUERY.CREATE.ACTIVITY_TAG);
  274. db.execSQL(GM.DB.QUERY.CREATE.ALBUM);
  275. db.execSQL(GM.DB.QUERY.CREATE.FESTIVAL);
  276. db.execSQL(GM.DB.QUERY.CREATE.FESTIVAL_DAY);
  277. db.execSQL(GM.DB.QUERY.CREATE.FESTIVAL_EVENT);
  278. db.execSQL(GM.DB.QUERY.CREATE.FESTIVAL_EVENT_IMAGE);
  279. db.execSQL(GM.DB.QUERY.CREATE.FESTIVAL_OFFER);
  280. db.execSQL(GM.DB.QUERY.CREATE.PEOPLE);
  281. db.execSQL(GM.DB.QUERY.CREATE.PHOTO);
  282. db.execSQL(GM.DB.QUERY.CREATE.PHOTO_ALBUM);
  283. db.execSQL(GM.DB.QUERY.CREATE.PHOTO_COMMENT);
  284. db.execSQL(GM.DB.QUERY.CREATE.PLACE);
  285. db.execSQL(GM.DB.QUERY.CREATE.POST);
  286. db.execSQL(GM.DB.QUERY.CREATE.POST_COMMENT);
  287. db.execSQL(GM.DB.QUERY.CREATE.POST_IMAGE);
  288. db.execSQL(GM.DB.QUERY.CREATE.POST_TAG);
  289. db.execSQL(GM.DB.QUERY.CREATE.SETTINGS);
  290. db.execSQL(GM.DB.QUERY.CREATE.SPONSOR);
  291. db.execSQL(GM.DB.QUERY.CREATE.VERSION);
  292. //TODO: Is this really necessary?
  293. Thread.sleep(1000);
  294. } catch (InterruptedException e) {
  295. Log.e("SYNC", "Couldn't wait for the database to be recreated: " + e.toString());
  296. }
  297. catch (Exception ex) {
  298. result = false;
  299. Log.e("SYNC", "Error recreating the database: " + ex.toString());
  300. }
  301. return result;
  302. }
  303. /**
  304. * Finds out the type of a column of a table in the database.
  305. *
  306. * @param db A database connection.
  307. * @param table The name of the table.
  308. * @param idx Index of the column in the database (starting on 0).
  309. *
  310. * @return GM.DB.COLUMN.INT, GM.DB.COLUMN.DATETIME, GM.DB.COLUMN.VARCHAR (default).
  311. */
  312. private int getColumnType(SQLiteDatabase db, String table, int idx) {
  313. String type = "";
  314. Cursor typeCursor;
  315. try {
  316. String Query = "PRAGMA table_info(" + table + ")";
  317. typeCursor = db.rawQuery(Query, null);
  318. typeCursor.moveToPosition(idx);
  319. type = typeCursor.getString(2);
  320. typeCursor.close();
  321. }
  322. catch(Exception ex){
  323. Log.e("SYNC", "Unable to reliably determine the type of the column '" + idx + "' of the table " + table + ": " + ex.toString());
  324. }
  325. if (type.equals("INT")) {
  326. return GM.DB.COLUMN.INT;
  327. }
  328. if (type.equals("DATETIME")) {
  329. return GM.DB.COLUMN.DATETIME;
  330. }
  331. return GM.DB.COLUMN.VARCHAR;
  332. }
  333. /**
  334. * Stores version data for each section in the database.
  335. * Uses a custom JSON parser.
  336. *
  337. * @param db Database store the data.
  338. * @param versions List of strings with data about the versions.
  339. *
  340. * @return False if there were errors, true otherwise.
  341. */
  342. private boolean saveVersions(SQLiteDatabase db, String versions){
  343. String str = versions;
  344. String key;
  345. boolean result = true;
  346. int value;
  347. int totalVersions = 0;
  348. //If I ever have a database with more than 99 public sections (not tables), I'll have to change this. Also, ask for a raise.
  349. int[] values = new int[99];
  350. String[] keys = new String[99];
  351. try {
  352. while (str.indexOf("{") > 0){
  353. key = str.substring(str.indexOf("{\"") + 2, str.indexOf("\":"));
  354. str = str.substring(str.indexOf("\":\"") + 3);
  355. value = Integer.parseInt(str.substring(0, str.indexOf("\"")));
  356. str = str.substring(str.indexOf("}") + 1);
  357. keys[totalVersions] = key;
  358. values[totalVersions] = value;
  359. totalVersions ++;
  360. }
  361. if (totalVersions > 0 && totalVersions < 99){
  362. db.execSQL(GM.DB.QUERY.CREATE.VERSION);
  363. db.execSQL(GM.DB.QUERY.EMPTY.VERSION);
  364. for (int i = 0; i < totalVersions; i ++){
  365. db.execSQL("INSERT INTO version VALUES ('" + keys[i] + "', " + values[i] + ");");
  366. }
  367. }
  368. else{
  369. result = false;
  370. }
  371. }
  372. catch (Exception ex){
  373. result = false;
  374. Log.e("SYNC", "Error saving the remote db versions: " + ex.toString());
  375. }
  376. return result;
  377. }
  378. /**
  379. * Stores version data for each section in the database.
  380. * Uses a custom JSON parser.
  381. *
  382. * @param db Database store the data.
  383. * @param data List of strings in json format with the tables.
  384. *
  385. * @return False if there were errors, true otherwise.
  386. */
  387. private boolean saveData(SQLiteDatabase db, String data){
  388. String str;
  389. String key;
  390. String value;
  391. boolean result = true;
  392. try{
  393. str = data.substring(data.indexOf("[") + 1, data.lastIndexOf("]"));
  394. while (str.indexOf("}") > 0){
  395. try {
  396. key = str.substring(str.indexOf("\"") + 1, str.indexOf("\"", str.indexOf(":") - 1));
  397. value = str.substring(str.indexOf("[") + 1, str.indexOf("]"));
  398. str = str.substring(str.indexOf("]") + 1);
  399. if (!saveTable(db, key, value)){
  400. //Finish the loop if there is an error
  401. return false;
  402. }
  403. str = str.substring(str.indexOf("}") + 1);
  404. }
  405. catch (Exception ex){
  406. //End of string
  407. Log.d("SYNC", "End of data string:" + ex.toString());
  408. str = "";
  409. }
  410. }
  411. }
  412. catch (Exception ex){
  413. Log.e("SYNC", "Error saving the remote db data: " + ex.toString());
  414. result = false;
  415. }
  416. return result;
  417. }
  418. /**
  419. * Stores a table data into the database.
  420. * Uses a custom JSON parser.
  421. *
  422. * @param db Database to store the data.
  423. * @param table Name of the table.
  424. * @param data String in JSON format with the data of the table.
  425. * @return False if there were errors, true otherwise.
  426. */
  427. private boolean saveTable(SQLiteDatabase db, String table, String data) {
  428. int type, i;
  429. JSONObject jsonObj;
  430. String[] values = new String[99];
  431. String[] columns = new String[99];
  432. String val, row;
  433. String str = data;
  434. List<String> queries = new ArrayList<>();
  435. //The first query has to delete all entries
  436. queries.add("DELETE FROM " + table + ";");
  437. //Process each row
  438. while (str.contains("{\"") && str.contains("\"}")) {
  439. row = str.substring(str.indexOf("{\""), str.indexOf("\"}") + 2);
  440. i = 0;
  441. try {
  442. //Create a json object with the collumn names and values of the row...
  443. jsonObj = new JSONObject(row);
  444. Iterator<?> keys = jsonObj.keys();
  445. //... and loop through it.
  446. while (keys.hasNext()) {
  447. String key = (String) keys.next();
  448. String value = jsonObj.get(key).toString();
  449. columns[i] = key;
  450. values[i] = value;
  451. i++;
  452. }
  453. //Start building the query
  454. String q = "INSERT INTO " + table + "(";
  455. //Add column names
  456. for (int j = 0; j < i; j++) {
  457. q = q + columns[j] + ", ";
  458. }
  459. q = q.substring(0, q.length() - 2) + ") VALUES (";
  460. //Loop trough values
  461. for (int j = 0; j < i; j++) {
  462. //If the value is empty, I dont care what tipe is it, it will be 'null'.
  463. if (values[j].length() == 0) {
  464. val = "null";
  465. q = q + val + ", ";
  466. }
  467. //If the value is not empty, I need the type
  468. else {
  469. type = getColumnType(db, table, j);
  470. switch (type) {
  471. case GM.DB.COLUMN.INT:
  472. //Check that is really a number to prevent injection.
  473. if (Pattern.matches("\\-?\\d+", values[j]))
  474. q = q + values[j] + ", ";
  475. else{
  476. //If its not and actually number, just insert null to avoid problems.
  477. q = q + "null, ";
  478. }
  479. break;
  480. case GM.DB.COLUMN.DATETIME:
  481. //The API formats ('YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS') are good for inserting. Just escape and add quotes.
  482. q = q + DatabaseUtils.sqlEscapeString(values[j]) + ", ";
  483. break;
  484. default: //VARCHAR
  485. //Just escape and add quotes.
  486. q = q + DatabaseUtils.sqlEscapeString(values[j]) + ", ";
  487. }
  488. }
  489. }
  490. //End the query string and add it to the array.
  491. q = q.substring(0, q.length() - 2) + ")";
  492. queries.add(q);
  493. }
  494. catch (JSONException e) {
  495. Log.e("SYNC", "Error parsing table '" + table + "': " + e.toString());
  496. return false;
  497. }
  498. catch (Exception ex) {
  499. Log.e("SYNC", "Error inserting data from remote table " + table + " into the local db: " + ex.toString());
  500. return false;
  501. }
  502. //Process str for the next loop pass.
  503. str = str.substring(str.indexOf("\"}") + 2);
  504. }
  505. //If I get to this point, there were no errors, and I can safely execute the queries
  506. try {
  507. int totalQueries = queries.size();
  508. for (i = 0; i < totalQueries; i++) {
  509. db.execSQL(queries.get(i));
  510. }
  511. } catch (Exception ex) {
  512. Log.e("SYNC", "Error inserting data from remote table " + table + " into the local db: " + ex.toString().substring(18 * ex.toString().length() / 20));
  513. //I don't put a 'return false;' here because I dont want to loose the whole table for just one row.
  514. }
  515. return true;
  516. }
  517. /**
  518. * The sweet stuff. Actually performs the sync.
  519. */
  520. @Override
  521. protected Void doInBackground(Void... params) {
  522. //Get preferences
  523. SharedPreferences preferences = myContextRef.getSharedPreferences(GM.DATA.DATA, Context.MODE_PRIVATE);
  524. //Get useful data for the uri
  525. String userCode = preferences.getString(GM.DATA.KEY.USER, GM.DATA.DEFAULT.USER);
  526. //Get database. Stop if it's locked
  527. SQLiteDatabase db = myContextRef.openOrCreateDatabase(GM.DB.NAME, Activity.MODE_PRIVATE, null);
  528. if (db.isReadOnly()){
  529. Log.e("SYNC", "Database is locked and in read only mode. Skipping sync.");
  530. return null;
  531. }
  532. //Get database version
  533. Cursor cursor;
  534. int dbVersion;
  535. cursor = db.rawQuery("SELECT sum(version) AS v FROM version;", null);
  536. cursor.moveToFirst();
  537. dbVersion = cursor.getInt(0);
  538. cursor.close();
  539. URL url;
  540. String uri = buildUrl(userCode, dbVersion, fg, GM.getLang());
  541. try {
  542. url = new URL(uri);
  543. HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
  544. int httpCode = urlConnection.getResponseCode();
  545. switch (httpCode){
  546. case 400: //Client error: Bad request
  547. Log.e("SYNC", "The server returned a 400 code (Client Error: Bad request) for the url \"" + uri + "\"");
  548. break;
  549. case 403: //Client error: Forbidden
  550. Log.e("SYNC", "The server returned a 403 code (Client Error: Forbidden) for the url \"" + uri + "\"");
  551. break;
  552. case 204: //Success: No content
  553. Log.d("SYNC", "The server returned a 204 code (Success: No content) for the url \"" + uri + "\". Stopping sync process...");
  554. break;
  555. case 200: //Success: OK
  556. Log.d("SYNC", "The server returned a 200 code (Success: OK) for the url \"" + uri + "\". Now syncing...");
  557. BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
  558. StringBuilder sb = new StringBuilder();
  559. String o;
  560. while ((o = br.readLine()) != null)
  561. sb.append(o);
  562. //Get the string with the sync json. (The whole page)
  563. String strSync = sb.toString();
  564. strSync = strSync.replace(":null", ":\"\"");
  565. //Get the JSON object from the string.
  566. JSONObject jsonSync = new JSONObject(strSync);
  567. jsonSync = new JSONObject(jsonSync.get("sync").toString().substring(1, jsonSync.get("sync").toString().length() - 1));
  568. //Get the string wit the versions in JSON format.
  569. String strVersion = jsonSync.get("version").toString();
  570. //Handmade parser, because with:
  571. //String strData = jsonSync.get("data").toString();
  572. //I get an error: "No value for data"
  573. String strData = strSync.substring(1, strSync.length() - 1);
  574. strData = strData.substring(strData.indexOf("{\"data\":"));
  575. //If the data is correctly parsed and stored, commit changes to the database.
  576. db.beginTransaction();
  577. //TODO: for save version apps, this is not needed, but I have to check it after an app update.
  578. //recreateDb(db);
  579. if (saveVersions(db, strVersion) && saveData(db, strData)){
  580. db.setTransactionSuccessful();
  581. Log.d("SYNC", "The sync process finished correctly. Changes to the database will be commited");
  582. }
  583. else{
  584. Log.e("SYNC", "The sync process did not finish correctly. Any changes made to the database will be reverted");
  585. }
  586. db.endTransaction();
  587. break;
  588. default:
  589. Log.e("SYNC", "The server returned an unexpected code (" + httpCode + ") for the url \"" + uri + "\"");
  590. }
  591. urlConnection.disconnect();
  592. return null;
  593. }
  594. catch (MalformedURLException e) {
  595. Log.e("SYNC", "Malformed URL (" + uri + "): " + e.toString());
  596. e.printStackTrace();
  597. }
  598. catch (IOException e) {
  599. Log.e("SYNC", "IOException for URL (" + uri + "): " + e.toString());
  600. e.printStackTrace();
  601. }
  602. catch (org.json.JSONException e) {
  603. Log.e("SYNC", "JSONException for URL (" + uri + "): " + e.toString());
  604. e.printStackTrace();
  605. }
  606. db.close();
  607. return null;
  608. }
  609. }