]> git.ktnx.net Git - mobile-ledger.git/blobdiff - app/src/main/java/net/ktnx/mobileledger/RetrieveTransactionsTask.java
machinery for retrieving transaction journal from hledger-web
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / RetrieveTransactionsTask.java
diff --git a/app/src/main/java/net/ktnx/mobileledger/RetrieveTransactionsTask.java b/app/src/main/java/net/ktnx/mobileledger/RetrieveTransactionsTask.java
new file mode 100644 (file)
index 0000000..4131a21
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.AsyncTask;
+
+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 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;
+    }
+}
+
+class RetrieveTransactionsTask extends AsyncTask<Params, Integer, Void> {
+    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;
+    }
+}