package net.ktnx.mobileledger;
+import android.database.sqlite.SQLiteDatabase;
+
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
class LedgerTransaction {
+ private String id;
private String date;
private String description;
private List<LedgerTransactionItem> items;
- LedgerTransaction(String date, String description) {
+ LedgerTransaction(String id, String date, String description) {
+ this.id = id;
this.date = date;
this.description = description;
this.items = new ArrayList<>();
}
-
+ LedgerTransaction(String date, String description) {
+ this(null, date, description);
+ }
void add_item(LedgerTransactionItem item) {
items.add(item);
}
}
};
}
+ public String getId() {
+ return id;
+ }
+
+ void insertInto(SQLiteDatabase db) {
+ db.execSQL("INSERT INTO transactions(id, date, " + "description) values(?, ?, ?)",
+ new String[]{id, date, description});
+
+ for(LedgerTransactionItem item : items) {
+ db.execSQL("INSERT INTO transaction_accounts(transaction_id, account_name, amount, "
+ + "currency) values(?, ?, ?, ?)", new Object[]{id, item.getAccountName(),
+ item.getAmount(), item.getCurrency()});
+ }
+ }
}
package net.ktnx.mobileledger;
class LedgerTransactionItem {
- private String account_name;
+ private String accountName;
private float amount;
- private boolean amount_set;
+ private boolean amountSet;
+ private String currency;
- LedgerTransactionItem(String account_name, float amount) {
- this.account_name = account_name;
+ LedgerTransactionItem(String accountName, float amount) {
+ this(accountName, amount, null);
+ }
+ LedgerTransactionItem(String accountName, float amount, String currency) {
+ this.accountName = accountName;
this.amount = amount;
- this.amount_set = true;
+ this.amountSet = true;
+ this.currency = currency;
}
- public LedgerTransactionItem(String account_name) {
- this.account_name = account_name;
+ public LedgerTransactionItem(String accountName) {
+ this.accountName = accountName;
}
- public String get_account_name() {
- return account_name;
+ public String getAccountName() {
+ return accountName;
}
- public void set_account_name(String account_name) {
- this.account_name = account_name;
+ public void setAccountName(String accountName) {
+ this.accountName = accountName;
}
- public float get_amount() {
- if (!amount_set)
+ public float getAmount() {
+ if (!amountSet)
throw new IllegalStateException("Account amount is not set");
return amount;
}
- public void set_amount(float account_amount) {
+ public void setAmount(float account_amount) {
this.amount = account_amount;
- this.amount_set = true;
+ this.amountSet = true;
}
- public void reset_amount() {
- this.amount_set = false;
+ public void resetAmount() {
+ this.amountSet = false;
}
- public boolean is_amount_set() {
- return amount_set;
+ public boolean isAmountSet() {
+ return amountSet;
+ }
+ public String getCurrency() {
+ return currency;
}
}
--- /dev/null
+/*
+ * 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;
+ }
+}