increment background task count while retrieving accounts too
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / async / RetrieveAccountsTask.java
1 /*
2  * Copyright © 2019 Damyan Ivanov.
3  * This file is part of Mobile-Ledger.
4  * Mobile-Ledger is free software: you can distribute it and/or modify it
5  * under the term of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your opinion), any later version.
8  *
9  * Mobile-Ledger is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License terms for details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
16  */
17
18 package net.ktnx.mobileledger.async;
19
20 import android.content.SharedPreferences;
21 import android.database.sqlite.SQLiteDatabase;
22 import android.os.OperationCanceledException;
23 import android.util.Log;
24
25 import net.ktnx.mobileledger.R;
26 import net.ktnx.mobileledger.model.Data;
27 import net.ktnx.mobileledger.model.LedgerAccount;
28 import net.ktnx.mobileledger.ui.activity.MainActivity;
29 import net.ktnx.mobileledger.utils.MLDB;
30 import net.ktnx.mobileledger.utils.NetworkUtil;
31
32 import java.io.BufferedReader;
33 import java.io.FileNotFoundException;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.lang.ref.WeakReference;
38 import java.net.HttpURLConnection;
39 import java.net.MalformedURLException;
40 import java.net.URLDecoder;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
43
44 public class RetrieveAccountsTask extends android.os.AsyncTask<Void, Integer, Void> {
45     int error;
46     WeakReference<MainActivity> mContext;
47     private SharedPreferences pref;
48
49     public RetrieveAccountsTask(WeakReference<MainActivity> context) {
50         mContext = context;
51         error = 0;
52     }
53
54     public void setPref(SharedPreferences pref) {
55         this.pref = pref;
56     }
57
58     protected Void doInBackground(Void... params) {
59         Data.backgroundTaskCount.incrementAndGet();
60         try {
61             HttpURLConnection http = NetworkUtil.prepare_connection(pref, "add");
62             publishProgress(0);
63             try (SQLiteDatabase db = MLDB.getWritableDatabase()) {
64                 try (InputStream resp = http.getInputStream()) {
65                     Log.d("update_accounts", String.valueOf(http.getResponseCode()));
66                     if (http.getResponseCode() != 200) {
67                         throw new IOException(
68                                 String.format("HTTP error: %d %s", http.getResponseCode(),
69                                         http.getResponseMessage()));
70                     }
71                     else {
72                         if (db.inTransaction()) throw new AssertionError();
73
74                         db.beginTransaction();
75
76                         try {
77                             db.execSQL("update account_values set keep=0;");
78                             db.execSQL("update accounts set keep=0;");
79
80                             String line;
81                             String last_account_name = null;
82                             BufferedReader buf =
83                                     new BufferedReader(new InputStreamReader(resp, "UTF-8"));
84                             // %3A is '='
85                             Pattern account_name_re =
86                                     Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\"");
87                             Pattern account_value_re = Pattern.compile(
88                                     "<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
89                             Pattern tr_end_re = Pattern.compile("</tr>");
90                             Pattern descriptions_line_re =
91                                     Pattern.compile("\\bdescriptionsSuggester\\s*=\\s*new\\b");
92                             Pattern description_items_re =
93                                     Pattern.compile("\"value\":\"([^\"]+)\"");
94                             int count = 0;
95                             while ((line = buf.readLine()) != null) {
96                                 throwIfCancelled();
97
98                                 Matcher m = account_name_re.matcher(line);
99                                 if (m.find()) {
100                                     String acct_encoded = m.group(1);
101                                     String acct_name = URLDecoder.decode(acct_encoded, "UTF-8");
102                                     acct_name = acct_name.replace("\"", "");
103                                     Log.d("account-parser", acct_name);
104
105                                     addAccount(db, acct_name);
106                                     publishProgress(++count);
107
108                                     last_account_name = acct_name;
109
110                                     continue;
111                                 }
112
113                                 Matcher tr_m = tr_end_re.matcher(line);
114                                 if (tr_m.find()) {
115                                     Log.d("account-parser", "<tr> - another account expected");
116                                     last_account_name = null;
117                                     continue;
118                                 }
119
120                                 if (last_account_name != null) {
121                                     m = account_value_re.matcher(line);
122                                     boolean match_found = false;
123                                     while (m.find()) {
124                                         throwIfCancelled();
125
126                                         match_found = true;
127                                         String value = m.group(1);
128                                         String currency = m.group(2);
129                                         if (currency == null) currency = "";
130                                         value = value.replace(',', '.');
131                                         Log.d("db", "curr=" + currency + ", value=" + value);
132                                         db.execSQL(
133                                                 "insert or replace into account_values(account, currency, value, keep) values(?, ?, ?, 1);",
134                                                 new Object[]{last_account_name, currency,
135                                                              Float.valueOf(value)
136                                                 });
137                                     }
138
139                                     if (match_found) continue;
140                                 }
141
142                                 m = descriptions_line_re.matcher(line);
143                                 if (m.find()) {
144                                     db.execSQL("update description_history set keep=0;");
145                                     m = description_items_re.matcher(line);
146                                     while (m.find()) {
147                                         throwIfCancelled();
148
149                                         String description = m.group(1);
150                                         if (description.isEmpty()) continue;
151
152                                         Log.d("db", String.format("Stored description: %s",
153                                                 description));
154                                         db.execSQL("insert or replace into description_history" +
155                                                    "(description, description_upper, keep) " +
156                                                    "values(?, ?, 1);",
157                                                 new Object[]{description, description.toUpperCase()
158                                                 });
159                                     }
160                                 }
161                             }
162
163                             db.execSQL("delete from account_values where keep=0;");
164                             db.execSQL("delete from accounts where keep=0;");
165                             db.setTransactionSuccessful();
166                         }
167                         catch (OperationCanceledException e) {
168                             Log.w("async", "Account retrieval cancelled");
169                         }
170                         finally {
171                             db.endTransaction();
172                         }
173                     }
174                 }
175             }
176         }
177         catch (MalformedURLException e) {
178             error = R.string.err_bad_backend_url;
179             e.printStackTrace();
180         }
181         catch (FileNotFoundException e) {
182             error = R.string.err_bad_auth;
183             e.printStackTrace();
184         }
185         catch (IOException e) {
186             error = R.string.err_net_io_error;
187             e.printStackTrace();
188         }
189         catch (Exception e) {
190             error = R.string.err_net_error;
191             e.printStackTrace();
192         }
193         finally {
194             Log.d("RAT", "decrementing background task count");
195             Data.backgroundTaskCount.decrementAndGet();
196         }
197
198         return null;
199     }
200     private void throwIfCancelled() {
201         if (isCancelled()) throw new OperationCanceledException(null);
202     }
203
204     private void addAccount(SQLiteDatabase db, String name) {
205         do {
206             LedgerAccount acc = new LedgerAccount(name);
207             db.execSQL("update accounts set level = ?, keep = 1 where name = ?",
208                     new Object[]{acc.getLevel(), name});
209             db.execSQL("insert into accounts(name, name_upper, parent_name, level) select ?,?," +
210                        "?,? " + "where (select changes() = 0)",
211                     new Object[]{name, name.toUpperCase(), acc.getParentName(), acc.getLevel()});
212             name = acc.getParentName();
213         } while (name != null);
214     }
215     @Override
216     protected void onPostExecute(Void result) {
217         MainActivity ctx = mContext.get();
218         if (ctx == null) return;
219         ctx.onRetrieveDone(this.error == 0);
220     }
221
222 }