+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * Mobile-Ledger 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 terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.async;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.AsyncTask;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.LedgerTransaction;
+import net.ktnx.mobileledger.model.LedgerTransactionItem;
+import net.ktnx.mobileledger.utils.MobileLedgerDatabase;
+import net.ktnx.mobileledger.utils.NetworkUtil;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.ref.WeakReference;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+class RetrieveTransactionsTask extends AsyncTask<RetrieveTransactionsTask.Params, Integer, Void> {
+ class Params {
+ static final int DEFAULT_LIMIT = 100;
+ private SharedPreferences backendPref;
+ private String accountsRoot;
+ private int limit;
+
+ Params(SharedPreferences backendPref) {
+ this.backendPref = backendPref;
+ this.accountsRoot = null;
+ this.limit = DEFAULT_LIMIT;
+ }
+ Params(SharedPreferences backendPref, String accountsRoot) {
+ this(backendPref, accountsRoot, DEFAULT_LIMIT);
+ }
+ Params(SharedPreferences backendPref, String accountsRoot, int limit) {
+ this.backendPref = backendPref;
+ this.accountsRoot = accountsRoot;
+ this.limit = limit;
+ }
+ String getAccountsRoot() {
+ return accountsRoot;
+ }
+ SharedPreferences getBackendPref() {
+ return backendPref;
+ }
+ int getLimit() {
+ return limit;
+ }
+ }
+ private static final Pattern transactionStartPattern = Pattern.compile("<tr class=\"title\" "
+ + "id=\"transaction-(\\d+)\"><td class=\"date\"[^\\\"]*>([\\d.-]+)</td>");
+ private static final Pattern transactionDescriptionPattern =
+ Pattern.compile("<tr class=\"posting\" title=\"(\\S+)\\s(.+)");
+ private static final Pattern transactionDetailsPattern =
+ Pattern.compile("^\\s+" + "(\\S[\\S\\s]+\\S)\\s\\s+([-+]?\\d[\\d,.]*)");
+ protected WeakReference<Context> contextRef;
+ protected int error;
+ @Override
+ protected Void doInBackground(Params... params) {
+ try {
+ HttpURLConnection http =
+ NetworkUtil.prepare_connection(params[0].getBackendPref(), "journal");
+ http.setAllowUserInteraction(false);
+ publishProgress(0);
+ Context ctx = contextRef.get();
+ if (ctx == null) return null;
+ try (MobileLedgerDatabase dbh = new MobileLedgerDatabase(ctx)) {
+ try (SQLiteDatabase db = dbh.getWritableDatabase()) {
+ try (InputStream resp = http.getInputStream()) {
+ if (http.getResponseCode() != 200) throw new IOException(
+ String.format("HTTP error %d", http.getResponseCode()));
+ db.beginTransaction();
+ try {
+ String root = params[0].getAccountsRoot();
+ if (root == null) db.execSQL("DELETE FROM transaction_history;");
+ else {
+ StringBuilder sql = new StringBuilder();
+ sql.append("DELETE FROM transaction_history ");
+ sql.append(
+ "where id in (select transactions.id from transactions ");
+ sql.append("join transaction_accounts ");
+ sql.append(
+ "on transactions.id=transaction_accounts.transaction_id ");
+ sql.append("where transaction_accounts.account_name like ?||'%'");
+ db.execSQL(sql.toString(), new String[]{root});
+ }
+
+ int state = ParserState.EXPECTING_JOURNAL;
+ String line;
+ BufferedReader buf =
+ new BufferedReader(new InputStreamReader(resp, "UTF-8"));
+
+ int transactionCount = 0;
+ String transactionId = null;
+ LedgerTransaction transaction = null;
+ while ((line = buf.readLine()) != null) {
+ switch (state) {
+ case ParserState.EXPECTING_JOURNAL: {
+ if (line.equals("<h2>General Journal</h2>"))
+ state = ParserState.EXPECTING_TRANSACTION;
+ continue;
+ }
+ case ParserState.EXPECTING_TRANSACTION: {
+ Matcher m = transactionStartPattern.matcher(line);
+ if (m.find()) {
+ transactionId = m.group(1);
+ state = ParserState.EXPECTING_TRANSACTION_DESCRIPTION;
+ }
+ }
+ case ParserState.EXPECTING_TRANSACTION_DESCRIPTION: {
+ Matcher m = transactionDescriptionPattern.matcher(line);
+ if (m.find()) {
+ if (transactionId == null)
+ throw new TransactionParserException(
+ "Transaction Id is null while expecting description");
+
+ transaction =
+ new LedgerTransaction(transactionId, m.group(1),
+ m.group(2));
+ state = ParserState.EXPECTING_TRANSACTION_DETAILS;
+ }
+ }
+ case ParserState.EXPECTING_TRANSACTION_DETAILS: {
+ if (transaction == null)
+ throw new TransactionParserException(
+ "Transaction is null while expecting details");
+ if (line.isEmpty()) {
+ // transaction data collected
+ transaction.insertInto(db);
+
+ state = ParserState.EXPECTING_TRANSACTION;
+ publishProgress(++transactionCount);
+ }
+ else {
+ Matcher m = transactionDetailsPattern.matcher(line);
+ if (m.find()) {
+ String acc_name = m.group(1);
+ String amount = m.group(2);
+ amount = amount.replace(',', '.');
+ transaction.add_item(
+ new LedgerTransactionItem(acc_name,
+ Float.valueOf(amount)));
+ }
+ else throw new IllegalStateException(String.format(
+ "Can't" + " parse transaction details"));
+ }
+ }
+ default:
+ throw new RuntimeException(
+ String.format("Unknown " + "parser state %d",
+ state));
+ }
+ }
+ db.setTransactionSuccessful();
+ }
+ finally {
+ db.endTransaction();
+ }
+ }
+ }
+ }
+ }
+ catch (MalformedURLException e) {
+ error = R.string.err_bad_backend_url;
+ e.printStackTrace();
+ }
+ catch (FileNotFoundException e) {
+ error = R.string.err_bad_auth;
+ e.printStackTrace();
+ }
+ catch (IOException e) {
+ error = R.string.err_net_io_error;
+ e.printStackTrace();
+ }
+ return null;
+ }
+ WeakReference<Context> getContextRef() {
+ return contextRef;
+ }
+
+ private class TransactionParserException extends IllegalStateException {
+ TransactionParserException(String message) {
+ super(message);
+ }
+ }
+
+ private class ParserState {
+ static final int EXPECTING_JOURNAL = 0;
+ static final int EXPECTING_TRANSACTION = 1;
+ static final int EXPECTING_TRANSACTION_DESCRIPTION = 2;
+ static final int EXPECTING_TRANSACTION_DETAILS = 3;
+ }
+}