]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
separate packages for the different aspects of the application
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / async / RetrieveTransactionsTask.java
1 /*
2  * Copyright © 2018 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.Context;
21 import android.content.SharedPreferences;
22 import android.database.sqlite.SQLiteDatabase;
23 import android.os.AsyncTask;
24
25 import net.ktnx.mobileledger.R;
26 import net.ktnx.mobileledger.model.LedgerTransaction;
27 import net.ktnx.mobileledger.model.LedgerTransactionItem;
28 import net.ktnx.mobileledger.utils.MobileLedgerDatabase;
29 import net.ktnx.mobileledger.utils.NetworkUtil;
30
31 import java.io.BufferedReader;
32 import java.io.FileNotFoundException;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.InputStreamReader;
36 import java.lang.ref.WeakReference;
37 import java.net.HttpURLConnection;
38 import java.net.MalformedURLException;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42
43 class RetrieveTransactionsTask extends AsyncTask<RetrieveTransactionsTask.Params, Integer, Void> {
44     class Params {
45         static final int DEFAULT_LIMIT = 100;
46         private SharedPreferences backendPref;
47         private String accountsRoot;
48         private int limit;
49
50         Params(SharedPreferences backendPref) {
51             this.backendPref = backendPref;
52             this.accountsRoot = null;
53             this.limit = DEFAULT_LIMIT;
54         }
55         Params(SharedPreferences backendPref, String accountsRoot) {
56             this(backendPref, accountsRoot, DEFAULT_LIMIT);
57         }
58         Params(SharedPreferences backendPref, String accountsRoot, int limit) {
59             this.backendPref = backendPref;
60             this.accountsRoot = accountsRoot;
61             this.limit = limit;
62         }
63         String getAccountsRoot() {
64             return accountsRoot;
65         }
66         SharedPreferences getBackendPref() {
67             return backendPref;
68         }
69         int getLimit() {
70             return limit;
71         }
72     }
73     private static final Pattern transactionStartPattern = Pattern.compile("<tr class=\"title\" "
74             + "id=\"transaction-(\\d+)\"><td class=\"date\"[^\\\"]*>([\\d.-]+)</td>");
75     private static final Pattern transactionDescriptionPattern =
76             Pattern.compile("<tr class=\"posting\" title=\"(\\S+)\\s(.+)");
77     private static final Pattern transactionDetailsPattern =
78             Pattern.compile("^\\s+" + "(\\S[\\S\\s]+\\S)\\s\\s+([-+]?\\d[\\d,.]*)");
79     protected WeakReference<Context> contextRef;
80     protected int error;
81     @Override
82     protected Void doInBackground(Params... params) {
83         try {
84             HttpURLConnection http =
85                     NetworkUtil.prepare_connection(params[0].getBackendPref(), "journal");
86             http.setAllowUserInteraction(false);
87             publishProgress(0);
88             Context ctx = contextRef.get();
89             if (ctx == null) return null;
90             try (MobileLedgerDatabase dbh = new MobileLedgerDatabase(ctx)) {
91                 try (SQLiteDatabase db = dbh.getWritableDatabase()) {
92                     try (InputStream resp = http.getInputStream()) {
93                         if (http.getResponseCode() != 200) throw new IOException(
94                                 String.format("HTTP error %d", http.getResponseCode()));
95                         db.beginTransaction();
96                         try {
97                             String root = params[0].getAccountsRoot();
98                             if (root == null) db.execSQL("DELETE FROM transaction_history;");
99                             else {
100                                 StringBuilder sql = new StringBuilder();
101                                 sql.append("DELETE FROM transaction_history ");
102                                 sql.append(
103                                         "where id in (select transactions.id from transactions ");
104                                 sql.append("join transaction_accounts ");
105                                 sql.append(
106                                         "on transactions.id=transaction_accounts.transaction_id ");
107                                 sql.append("where transaction_accounts.account_name like ?||'%'");
108                                 db.execSQL(sql.toString(), new String[]{root});
109                             }
110
111                             int state = ParserState.EXPECTING_JOURNAL;
112                             String line;
113                             BufferedReader buf =
114                                     new BufferedReader(new InputStreamReader(resp, "UTF-8"));
115
116                             int transactionCount = 0;
117                             String transactionId = null;
118                             LedgerTransaction transaction = null;
119                             while ((line = buf.readLine()) != null) {
120                                 switch (state) {
121                                     case ParserState.EXPECTING_JOURNAL: {
122                                         if (line.equals("<h2>General Journal</h2>"))
123                                             state = ParserState.EXPECTING_TRANSACTION;
124                                         continue;
125                                     }
126                                     case ParserState.EXPECTING_TRANSACTION: {
127                                         Matcher m = transactionStartPattern.matcher(line);
128                                         if (m.find()) {
129                                             transactionId = m.group(1);
130                                             state = ParserState.EXPECTING_TRANSACTION_DESCRIPTION;
131                                         }
132                                     }
133                                     case ParserState.EXPECTING_TRANSACTION_DESCRIPTION: {
134                                         Matcher m = transactionDescriptionPattern.matcher(line);
135                                         if (m.find()) {
136                                             if (transactionId == null)
137                                                 throw new TransactionParserException(
138                                                         "Transaction Id is null while expecting description");
139
140                                             transaction =
141                                                     new LedgerTransaction(transactionId, m.group(1),
142                                                             m.group(2));
143                                             state = ParserState.EXPECTING_TRANSACTION_DETAILS;
144                                         }
145                                     }
146                                     case ParserState.EXPECTING_TRANSACTION_DETAILS: {
147                                         if (transaction == null)
148                                             throw new TransactionParserException(
149                                                     "Transaction is null while expecting details");
150                                         if (line.isEmpty()) {
151                                             // transaction data collected
152                                             transaction.insertInto(db);
153
154                                             state = ParserState.EXPECTING_TRANSACTION;
155                                             publishProgress(++transactionCount);
156                                         }
157                                         else {
158                                             Matcher m = transactionDetailsPattern.matcher(line);
159                                             if (m.find()) {
160                                                 String acc_name = m.group(1);
161                                                 String amount = m.group(2);
162                                                 amount = amount.replace(',', '.');
163                                                 transaction.add_item(
164                                                         new LedgerTransactionItem(acc_name,
165                                                                 Float.valueOf(amount)));
166                                             }
167                                             else throw new IllegalStateException(String.format(
168                                                     "Can't" + " parse transaction details"));
169                                         }
170                                     }
171                                     default:
172                                         throw new RuntimeException(
173                                                 String.format("Unknown " + "parser state %d",
174                                                         state));
175                                 }
176                             }
177                             db.setTransactionSuccessful();
178                         }
179                         finally {
180                             db.endTransaction();
181                         }
182                     }
183                 }
184             }
185         }
186         catch (MalformedURLException e) {
187             error = R.string.err_bad_backend_url;
188             e.printStackTrace();
189         }
190         catch (FileNotFoundException e) {
191             error = R.string.err_bad_auth;
192             e.printStackTrace();
193         }
194         catch (IOException e) {
195             error = R.string.err_net_io_error;
196             e.printStackTrace();
197         }
198         return null;
199     }
200     WeakReference<Context> getContextRef() {
201         return contextRef;
202     }
203
204     private class TransactionParserException extends IllegalStateException {
205         TransactionParserException(String message) {
206             super(message);
207         }
208     }
209
210     private class ParserState {
211         static final int EXPECTING_JOURNAL = 0;
212         static final int EXPECTING_TRANSACTION = 1;
213         static final int EXPECTING_TRANSACTION_DESCRIPTION = 2;
214         static final int EXPECTING_TRANSACTION_DETAILS = 3;
215     }
216 }