Sync.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. package com.ivalentin.margolariak;
  2. import java.io.BufferedInputStream;
  3. import java.io.BufferedReader;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.io.InputStreamReader;
  7. import java.net.HttpURLConnection;
  8. import java.net.MalformedURLException;
  9. import java.net.URL;
  10. import java.net.URLEncoder;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. import android.app.Activity;
  14. import android.app.Dialog;
  15. import android.content.Context;
  16. import android.content.SharedPreferences;
  17. import android.database.sqlite.SQLiteDatabase;
  18. import android.os.AsyncTask;
  19. import android.util.Log;
  20. import android.view.View;
  21. import android.view.View.OnClickListener;
  22. import android.view.Window;
  23. import android.widget.Button;
  24. import android.widget.ImageView;
  25. import android.widget.ProgressBar;
  26. import android.widget.TextView;
  27. import org.json.*;
  28. /**
  29. * AsyncTask that synchronizes the online database to the device.
  30. * Is run every time the app is started, and periodically in the background.
  31. *
  32. * @author Inigo Valentin
  33. *
  34. */
  35. class Sync extends AsyncTask<Void, Void, Void> {
  36. private final Context myContextRef;
  37. private ProgressBar pbSync;
  38. private ImageView ivSync;
  39. private Dialog dialog;
  40. private MainActivity activity;
  41. private int fg;
  42. private int newVersion;
  43. private String strings[];
  44. private boolean doProgress = false;
  45. private long millis = 0;
  46. private TextView tv;
  47. /**
  48. * Things to do before sync. Namely, displaying a spinning progress bar.
  49. * @see android.os.AsyncTask#onPreExecute()
  50. */
  51. @Override
  52. protected void onPreExecute(){
  53. if (pbSync != null) {
  54. pbSync.setVisibility(View.VISIBLE);
  55. ivSync.setVisibility(View.GONE);
  56. }
  57. if (dialog != null) {
  58. dialog.show();
  59. strings = new String[10];
  60. strings[0] = myContextRef.getString(R.string.dialog_sync_text_0);
  61. strings[1] = myContextRef.getString(R.string.dialog_sync_text_1);
  62. strings[2] = myContextRef.getString(R.string.dialog_sync_text_2);
  63. strings[3] = myContextRef.getString(R.string.dialog_sync_text_3);
  64. strings[4] = myContextRef.getString(R.string.dialog_sync_text_4);
  65. strings[5] = myContextRef.getString(R.string.dialog_sync_text_5);
  66. strings[6] = myContextRef.getString(R.string.dialog_sync_text_6);
  67. strings[7] = myContextRef.getString(R.string.dialog_sync_text_7);
  68. strings[8] = myContextRef.getString(R.string.dialog_sync_text_8);
  69. strings[9] = myContextRef.getString(R.string.dialog_sync_text_8); //In case I get a 9;
  70. tv = (TextView) dialog.findViewById(R.id.tv_dialog_sync_text);
  71. int idx = (int) (Math.random() * 9);
  72. tv.setText(strings[idx]);
  73. doProgress = true;
  74. }
  75. Log.d("Sync", "Starting full sync");
  76. }
  77. /**
  78. * Things to do after sync. Namely, hiddng the spinning progress bar.
  79. * @see android.os.AsyncTask#onPreExecute()
  80. */
  81. @Override
  82. protected void onPostExecute(Void v){
  83. if (pbSync != null) {
  84. pbSync.setVisibility(View.GONE);
  85. ivSync.setVisibility(View.VISIBLE);
  86. }
  87. if (dialog != null){
  88. dialog.dismiss();
  89. //Check db version agan
  90. SharedPreferences preferences = myContextRef.getSharedPreferences(GM.PREF, Context.MODE_PRIVATE);
  91. if (preferences.getInt(GM.PREF_DB_VERSION, GM.DEFAULT_PREF_DB_VERSION) == GM.DEFAULT_PREF_DB_VERSION){
  92. //Create a dialog
  93. final Dialog dial = new Dialog(activity);
  94. dial.setCancelable(false);
  95. //Set up the window
  96. dial.requestWindowFeature(Window.FEATURE_NO_TITLE);
  97. dial.setContentView(R.layout.dialog_sync_failed);
  98. //Set button
  99. Button btClose = (Button) dial.findViewById(R.id.bt_dialog_sync_failed_close);
  100. btClose.setOnClickListener(new OnClickListener(){
  101. @Override
  102. public void onClick(View v) {
  103. dial.dismiss();
  104. activity.finish();
  105. }
  106. });
  107. //Show the dialog
  108. dial.show();
  109. }
  110. else{
  111. activity.loadSection(GM.SECTION_HOME);
  112. }
  113. }
  114. Log.d("Sync", "Full sync finished");
  115. }
  116. /**
  117. * Called when the AsyncTask is created.
  118. *
  119. * @param myContextRef The Context of the calling activity.
  120. */
  121. public Sync(Activity myContextRef){
  122. this.myContextRef = myContextRef;
  123. }
  124. /**
  125. * Called when the AsyncTask is created.
  126. *
  127. * @param myContextRef The Context of the calling activity.
  128. * @param pb The progress bar that will be shown while the sync goes on.
  129. */
  130. public Sync(Activity myContextRef, ProgressBar pb, ImageView iv) {
  131. this.myContextRef = myContextRef;
  132. dialog = null;
  133. pbSync = pb;
  134. ivSync = iv;
  135. fg = 1;
  136. }
  137. /**
  138. * Called when the AsyncTask is created.
  139. * This constructor is intended to use only in the first sync,
  140. * because a dialog will block the UI.
  141. *
  142. * @param myContextRef The Context of the calling activity.
  143. * @param d Dialog of the initial sync
  144. * @param pb The progress bar that will be shown while the sync goes on.
  145. * @param activity The calling MainActvity
  146. */
  147. public Sync(Activity myContextRef, ProgressBar pb, ImageView iv, Dialog d, MainActivity activity) {
  148. this.dialog = d;
  149. this.activity = activity;
  150. this.myContextRef = myContextRef;
  151. pbSync = pb;
  152. ivSync = iv;
  153. fg = 1;
  154. }
  155. /**
  156. * Called when the AsyncTask is created.
  157. *
  158. * @param context The Context of the calling activity.
  159. */
  160. public Sync(Context context) {
  161. this.myContextRef = context;
  162. pbSync = null;
  163. fg = 0;
  164. }
  165. @Override
  166. /**
  167. * Called when the AsyncTask is updated.
  168. * Used to change text in the sync window.
  169. */
  170. protected void onProgressUpdate(Void...progress) {
  171. if (doProgress){
  172. if (millis + 600 < System.currentTimeMillis()) {
  173. millis = System.currentTimeMillis();
  174. int idx = (int) (Math.random() * ((4) + 1));
  175. tv.setText(strings[idx]);
  176. }
  177. }
  178. }
  179. /**
  180. * Creates the URL required to performa a sync. Uses static data and data passed as arguments.
  181. *
  182. * @param user A unique user identifier.
  183. * @param version The db version of the client.
  184. * @param foreground 1 if the sync is done while the app is running, 0 otherwise.
  185. * @param lang Two letter language identifier.
  186. * @return The URL that will be used for syncing.
  187. */
  188. private String buildUrl(String user, int version, int foreground, String lang){
  189. String url = "";
  190. try {
  191. url = GM.SERVER + GM.SERVER_SYNC + "?" +
  192. GM.SERVER_SYNC_KEY_CLIENT + "=" + URLEncoder.encode(GM.CLIENT, "UTF-8") + "&" +
  193. GM.SERVER_SYNC_KEY_USER + "=" + URLEncoder.encode(user, "UTF-8") + "&" +
  194. GM.SERVER_SYNC_KEY_ACTION + "=" + URLEncoder.encode(GM.SERVER_SYNC_VALUE_ACTION, "UTF-8") + "&" +
  195. GM.SERVER_SYNC_KEY_SECTION + "=" + URLEncoder.encode(GM.SERVER_SYNC_VALUE_SECTION, "UTF-8") + "&" +
  196. GM.SERVER_SYNC_KEY_VERSION + "=" + version + "&" +
  197. GM.SERVER_SYNC_KEY_FOREGROUND + "=" + foreground + "&" +
  198. GM.SERVER_SYNC_KEY_FORMAT + "=" + URLEncoder.encode(GM.SERVER_SYNC_VALUE_FORMAT, "UTF-8") + "&" +
  199. GM.SERVER_SYNC_KEY_LANG + "=" + URLEncoder.encode(lang, "UTF-8");
  200. }
  201. catch (java.io.UnsupportedEncodingException ex){
  202. Log.e("UTF-8", "Error encoding url for sync \"" + url + "\" - " + ex.toString());
  203. }
  204. return url;
  205. }
  206. /**
  207. * Stores version data for each section in the database.
  208. * Uses a custom JSON parser.
  209. *
  210. * @param db Database store the data.
  211. * @param versions List of strings with data about the versions.
  212. * @return False if there were errors, true otherwise.
  213. */
  214. protected boolean saveVersions(SQLiteDatabase db, String versions){
  215. String str = versions;
  216. String key;
  217. boolean error = false;
  218. int value;
  219. int totalVersions = 0;
  220. int[] values = new int[99];
  221. String[] keys = new String[99];
  222. try {
  223. while (str.indexOf("{") > 0){
  224. key = str.substring(str.indexOf("{\"") + 2, str.indexOf("\":"));
  225. str = str.substring(str.indexOf("\":\"") + 3);
  226. value = Integer.parseInt(str.substring(0, str.indexOf("\"")));
  227. str = str.substring(str.indexOf("}") + 1);
  228. keys[totalVersions] = key;
  229. values[totalVersions] = value;
  230. totalVersions ++;
  231. }
  232. if (totalVersions > 0){
  233. db.execSQL("CREATE TABLE IF NOT EXISTS version (section VARCHAR, version INT);");
  234. db.execSQL("DELETE FROM version;");
  235. for (int i = 0; i < totalVersions; i ++){
  236. db.execSQL("INSERT INTO version VALUES ('" + keys[i] + "', " + values[i] + ");");
  237. }
  238. }
  239. }
  240. catch (Exception ex){
  241. error = true;
  242. Log.e("saveVersions", "Error saving the remote db versions: " + ex.toString());
  243. }
  244. return error;
  245. }
  246. /**
  247. * Stores version data for each section in the database.
  248. * Uses a custom JSON parser.
  249. *
  250. * @param db Database store the data.
  251. * @param data List of strings in json format with the tables.
  252. * @return False if there were errors, true otherwise.
  253. */
  254. protected boolean saveData(SQLiteDatabase db, String data){
  255. return true;
  256. }
  257. /**
  258. * The sweet stuff. Actually performs the sync.
  259. */
  260. @Override
  261. protected Void doInBackground(Void... params) {
  262. //Get preferences
  263. SharedPreferences preferences = myContextRef.getSharedPreferences(GM.PREF, Context.MODE_PRIVATE);
  264. SharedPreferences.Editor prefEditor;
  265. prefEditor = preferences.edit();
  266. //Get usefull data for the uri
  267. String userCode = preferences.getString(GM.USER_CODE, "");
  268. int dbVersion = preferences.getInt(GM.PREF_DB_VERSION, GM.DEFAULT_PREF_DB_VERSION);
  269. //Get database. Stop if it's locked
  270. SQLiteDatabase db = myContextRef.openOrCreateDatabase(GM.DB_NAME, Activity.MODE_PRIVATE, null);
  271. if (db.isReadOnly()){
  272. Log.e("Db ro", "Database is locked and in read only mode. Skipping sync.");
  273. return null;
  274. }
  275. URL url;
  276. String uri = buildUrl(userCode, dbVersion, fg, GM.getLang());
  277. try {
  278. url = new URL(uri);
  279. HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
  280. int httpCode = urlConnection.getResponseCode();
  281. switch (httpCode){
  282. case 400: //Client error: Bad request
  283. Log.e("Sync error", "The server returned a 400 code (Client Error: Bad request) for the url \"" + uri + "\"");
  284. break;
  285. case 403: //Client error: Forbidden
  286. Log.e("Sync error", "The server returned a 403 code (Client Error: Forbidden) for the url \"" + uri + "\"");
  287. break;
  288. case 204: //Success: No content
  289. Log.d("Sync success", "The server returned a 204 code (Success: No content) for the url \"" + uri + "\". Stoping sync process...");
  290. break;
  291. case 200: //Success: OK
  292. Log.d("Sync", "The server returned a 200 code (Success: OK) for the url \"" + uri + "\". Now syncing...");
  293. InputStream in = new BufferedInputStream(urlConnection.getInputStream());
  294. BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
  295. StringBuilder sb = new StringBuilder();
  296. String o;
  297. while ((o = br.readLine()) != null)
  298. sb.append(o);
  299. //Get the string with the sync json. (The whole page)
  300. String strSync = sb.toString();
  301. //Get the JSON object from the string.
  302. JSONObject jsonSync = new JSONObject(strSync);
  303. jsonSync = new JSONObject(jsonSync.get("sync").toString().substring(1, jsonSync.get("sync").toString().length() - 1));
  304. //Get the string wit the versions in JSON format.
  305. String strVersion = jsonSync.get("version").toString();
  306. //Get the string with the data in JSON format,
  307. saveVersions(db, strVersion);
  308. //saveData(db, data);
  309. break;
  310. default:
  311. Log.e("Sync error", "The server returned an unexpected code (" + httpCode + ") for the url \"" + uri + "\"");
  312. }
  313. urlConnection.disconnect();
  314. return null;
  315. }
  316. catch (MalformedURLException e) {
  317. Log.e("Sync error", "Malformed URL (" + uri + "): " + e.toString());
  318. e.printStackTrace();
  319. }
  320. catch (IOException e) {
  321. Log.e("Sync error", "IOException for URL (" + uri + "): " + e.toString());
  322. e.printStackTrace();
  323. }
  324. catch (org.json.JSONException e) {
  325. Log.e("Sync error", "JSONException for URL (" + uri + "): " + e.toString());
  326. e.printStackTrace();
  327. }
  328. /*//Open the database. f its locked, exit.
  329. SQLiteDatabase db = myContextRef.openOrCreateDatabase(GM.DB_NAME, Activity.MODE_PRIVATE, null);
  330. if (db.isReadOnly()){
  331. Log.e("Db ro", "Database is locked and in read only mode. Skipping sync.");
  332. return null;
  333. }
  334. publishProgress();
  335. //Gets the remote page.
  336. FetchURL fu;
  337. //The lines of the received web page.
  338. String o = null;
  339. //List of SQL queries to be performed, as received from the web page.
  340. List<String> queryList = new ArrayList<>();
  341. //Preferences.
  342. SharedPreferences preferences = myContextRef.getSharedPreferences(GM.PREF, Context.MODE_PRIVATE);
  343. SharedPreferences.Editor prefEditor;
  344. prefEditor = preferences.edit();
  345. //Boolean that will prevent the process to go on if something goes wrong.
  346. boolean success = true;
  347. //Get the file
  348. try{
  349. publishProgress();
  350. String code = preferences.getString(GM.USER_CODE, "");
  351. int dbVersion = preferences.getInt(GM.PREF_DB_VERSION, GM.DEFAULT_PREF_DB_VERSION);
  352. fu = new FetchURL();
  353. fu.Run(GM.SERVER + "/app/sync.php?os=android&code=" + code + "&fg=" + fg + "&v=" + dbVersion + "&lang=" + GM.getLang());
  354. //All the info
  355. o = fu.getOutput().toString();
  356. publishProgress();
  357. }
  358. catch(Exception ex){
  359. publishProgress();
  360. Log.e("Sync error", "Error fetching remote file: " + ex.toString());
  361. success = false;
  362. }
  363. int errorCount = 0;
  364. if (success){
  365. publishProgress();
  366. //Parse the contents of the page
  367. //Check if the database is synced
  368. if (o.contains("<synced>1</synced>")){
  369. Log.i("SYNC", "Database is already at the latest version");
  370. return null;
  371. }
  372. //Try to separate the file by tables (<table></table>)
  373. try{
  374. String table, tableName, row, fieldName, fieldValue, line, query, queryFields, queryValues;
  375. //Get db version
  376. newVersion = Integer.parseInt(o.substring(o.indexOf("<version>") + 9, o.indexOf("</version>")));
  377. //Get other preferences and store them
  378. int prefPhotos = Integer.parseInt(o.substring(o.indexOf("<photos>") + 8, o.indexOf("</photos>")));
  379. int prefFestivals = Integer.parseInt(o.substring(o.indexOf("<festivals>") + 11, o.indexOf("</festivals>")));
  380. prefEditor.putInt(GM.PREF_DB_PHOTOS, prefPhotos);
  381. prefEditor.putInt(GM.PREF_DB_FESTIVALS, prefFestivals);
  382. prefEditor.apply();
  383. while (o.contains("<table>")){
  384. publishProgress();
  385. try {
  386. table = o.substring(o.indexOf("<table>"), o.indexOf("</table>") + 8);
  387. tableName = table.substring(table.indexOf("<name>") + 6, table.indexOf("</name>"));
  388. queryList.add("DELETE FROM " + tableName + ";");
  389. while (table.contains("<row>")) {
  390. try {
  391. if (table.length() < 5) {
  392. break;
  393. }
  394. row = table.substring(table.indexOf("<row>") + 5, table.indexOf("</row>"));
  395. //if (row.indexOf("Gracias a todos vosotros no hemos hecho") != -1)
  396. // Log.e("Row", row);
  397. query = "INSERT INTO " + tableName + " ";
  398. queryFields = "(";
  399. queryValues = "(";
  400. while (row.contains(">,")) {
  401. publishProgress();
  402. try {
  403. if (row.length() < 5) {
  404. break;
  405. }
  406. line = row.substring(0, row.indexOf(">, \t") + 1);
  407. line = line.substring(line.indexOf("<"));
  408. fieldName = line.substring(line.indexOf("<") + 1, line.indexOf(">"));
  409. queryFields = queryFields + fieldName + ", ";
  410. fieldValue = line.substring(line.indexOf("<" + fieldName + ">") + 2 + fieldName.length());
  411. //fieldValue = fieldValue.substring(0, fieldValue.length() - fieldName.length() - 2);
  412. fieldValue = fieldValue.substring(0, fieldValue.indexOf("</" + fieldName + ">"));
  413. //if (fieldValue.indexOf("Gracias a todos vosotros no hemos hecho") != -1)
  414. //Log.e("Fieldvalue", fieldValue);
  415. if (fieldValue.length() == 0)
  416. fieldValue = "null";
  417. if (fieldValue.charAt(0) == '\'' && fieldValue.charAt(fieldValue.length() - 1) == '\'') {
  418. fieldValue = fieldValue.substring(1, fieldValue.length() - 1);
  419. fieldValue = fieldValue.replace("'", "''");
  420. fieldValue = "\'" + fieldValue + "\'";
  421. }
  422. //Log.e(fieldName, fieldValue);
  423. queryValues = queryValues + fieldValue + ", ";
  424. row = row.substring(row.indexOf(">, \t") + 4);
  425. }
  426. catch(Exception ex){
  427. Log.e("Parsing error", "Error getting values from row: " + ex.toString());
  428. errorCount ++;
  429. break;
  430. }
  431. }
  432. queryFields = queryFields.substring(0, queryFields.length() - 2) + ")";
  433. queryValues = queryValues.substring(0, queryValues.length() - 2) + ")";
  434. query = query + queryFields + " VALUES " + queryValues + ";";
  435. queryList.add(query);
  436. //Log.e("Query", query);
  437. table = table.substring(table.indexOf("</row>") + 6);
  438. }
  439. catch(Exception ex){
  440. Log.e("Parsing error", "Error getting rows from tables: " + ex.toString());
  441. errorCount ++;
  442. break;
  443. }
  444. }
  445. }
  446. catch(Exception ex){
  447. Log.e("Parsing error", "Error getting tables from the sync file: " + ex.toString());
  448. errorCount ++;
  449. break;
  450. }
  451. o = o.substring(o.indexOf("</table>") + 8);
  452. }
  453. }
  454. catch(Exception ex){
  455. Log.e("Sync error", "Error parsing remote info: " + ex.toString());
  456. errorCount ++;
  457. }
  458. }
  459. try{
  460. for(int i = 0; i < queryList.size(); i++){
  461. publishProgress();
  462. try {
  463. db.execSQL(queryList.get(i));
  464. }
  465. catch(Exception ex){
  466. Log.e("Query Error", "Error on sync query: " + ex.toString());
  467. errorCount ++;
  468. }
  469. }
  470. db.close();
  471. //Set current database version in preferences.
  472. if (errorCount == 0){
  473. prefEditor.putInt(GM.PREF_DB_VERSION, newVersion);
  474. prefEditor.apply();
  475. }
  476. else{
  477. Log.e("Db update", "Not updating db version because there were errors");
  478. if (preferences.getInt(GM.PREF_DB_VERSION, GM.DEFAULT_PREF_DB_VERSION) == GM.DEFAULT_PREF_DB_VERSION) {
  479. prefEditor.putInt(GM.PREF_DB_VERSION, GM.DEFAULT_PREF_DB_VERSION + 1);
  480. prefEditor.apply();
  481. }
  482. }
  483. }
  484. catch(Exception ex){
  485. Log.e("Sync error", "Error updating info: " + ex.toString());
  486. }*/
  487. return null;
  488. }
  489. }