X-Git-Url: https://git.ktnx.net/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Fasync%2FRetrieveTransactionsTask.java;h=86d4cc457f0155322808d720eb57074c5cdefce8;hb=d08ab8235d0fd152c772b2dd5ffa1ca5747f67b1;hp=3ba73936deaa875068acbe6cbf2989255ef53556;hpb=fe7de3bcefd231c054c08e3b591334e0c5fbea86;p=mobile-ledger.git diff --git a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java index 3ba73936..86d4cc45 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -22,13 +22,15 @@ import android.database.sqlite.SQLiteDatabase; import android.os.AsyncTask; import android.os.OperationCanceledException; +import androidx.annotation.NonNull; + import net.ktnx.mobileledger.App; import net.ktnx.mobileledger.err.HTTPException; -import net.ktnx.mobileledger.json.AccountListParser; -import net.ktnx.mobileledger.json.ParsedBalance; -import net.ktnx.mobileledger.json.ParsedLedgerAccount; -import net.ktnx.mobileledger.json.ParsedLedgerTransaction; -import net.ktnx.mobileledger.json.TransactionListParser; +import net.ktnx.mobileledger.json.v1_15.AccountListParser; +import net.ktnx.mobileledger.json.v1_15.ParsedBalance; +import net.ktnx.mobileledger.json.v1_15.ParsedLedgerAccount; +import net.ktnx.mobileledger.json.v1_15.ParsedLedgerTransaction; +import net.ktnx.mobileledger.json.v1_15.TransactionListParser; import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.LedgerAccount; import net.ktnx.mobileledger.model.LedgerTransaction; @@ -54,8 +56,6 @@ import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; -import androidx.annotation.NonNull; - import static net.ktnx.mobileledger.utils.Logger.debug; @@ -63,13 +63,17 @@ public class RetrieveTransactionsTask extends AsyncTask { private static final int MATCHING_TRANSACTIONS_LIMIT = 150; private static final Pattern reComment = Pattern.compile("^\\s*;"); - private static final Pattern reTransactionStart = Pattern.compile("([\\d.-]+)"); + private static final Pattern reTransactionStart = Pattern.compile( + "([\\d.-]+)"); private static final Pattern reTransactionDescription = Pattern.compile(" contextRef; // %3A is '=' private Pattern reAccountName = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\""); @@ -84,32 +88,63 @@ public class RetrieveTransactionsTask private static void L(String msg) { //debug("transaction-parser", msg); } + static LedgerTransactionAccount parseTransactionAccountLine(String line) { + Matcher m = reTransactionDetails.matcher(line); + if (m.find()) { + String postingStatus = m.group(1); + String acc_name = m.group(2); + String currencyPre = m.group(3); + String amount = m.group(4); + String currencyPost = m.group(5); + + String currency = null; + if ((currencyPre != null) && (currencyPre.length() > 0)) { + if ((currencyPost != null) && (currencyPost.length() > 0)) + return null; + currency = currencyPre; + } + else if ((currencyPost != null) && (currencyPost.length() > 0)) { + currency = currencyPost; + } + + amount = amount.replace(',', '.'); + + return new LedgerTransactionAccount(acc_name, Float.valueOf(amount), currency, null); + } + else { + return null; + } + } @Override protected void onProgressUpdate(Progress... values) { super.onProgressUpdate(values); MainActivity context = getContext(); - if (context == null) return; + if (context == null) + return; context.onRetrieveProgress(values[0]); } @Override protected void onPreExecute() { super.onPreExecute(); MainActivity context = getContext(); - if (context == null) return; + if (context == null) + return; context.onRetrieveStart(); } @Override protected void onPostExecute(String error) { super.onPostExecute(error); MainActivity context = getContext(); - if (context == null) return; + if (context == null) + return; context.onRetrieveDone(error); } @Override protected void onCancelled() { super.onCancelled(); MainActivity context = getContext(); - if (context == null) return; + if (context == null) + return; context.onRetrieveDone(null); } private String retrieveTransactionListLegacy() @@ -120,7 +155,6 @@ public class RetrieveTransactionsTask HashMap accountNames = new HashMap<>(); HashMap syntheticAccounts = new HashMap<>(); LedgerAccount lastAccount = null, prevAccount = null; - boolean onlyStarred = Data.optShowOnlyStarred.get(); HttpURLConnection http = NetworkUtil.prepareConnection(profile, "journal"); http.setAllowUserInteraction(false); @@ -172,7 +206,7 @@ public class RetrieveTransactionsTask // state of the database db.setTransactionSuccessful(); db.endTransaction(); - Data.accounts.setList(accountList); + profile.setAccounts(accountList); db.beginTransaction(); continue; } @@ -185,12 +219,15 @@ public class RetrieveTransactionsTask prevAccount = lastAccount; lastAccount = profile.tryLoadAccount(db, acct_name); - if (lastAccount == null) lastAccount = new LedgerAccount(acct_name); - else lastAccount.removeAmounts(); + if (lastAccount == null) + lastAccount = new LedgerAccount(profile, acct_name); + else + lastAccount.removeAmounts(); profile.storeAccount(db, lastAccount); - if (prevAccount != null) prevAccount - .setHasSubAccounts(prevAccount.isParentOf(lastAccount)); + if (prevAccount != null) + prevAccount.setHasSubAccounts( + prevAccount.isParentOf(lastAccount)); // make sure the parent account(s) are present, // synthesising them if necessary // this happens when the (missing-in-HTML) parent account has @@ -200,24 +237,25 @@ public class RetrieveTransactionsTask if (parentName != null) { Stack toAppend = new Stack<>(); while (parentName != null) { - if (accountNames.containsKey(parentName)) break; + if (accountNames.containsKey(parentName)) + break; toAppend.push(parentName); - parentName = new LedgerAccount(parentName).getParentName(); + parentName = new LedgerAccount(profile, parentName).getParentName(); } syntheticAccounts.clear(); while (!toAppend.isEmpty()) { String aName = toAppend.pop(); LedgerAccount acc = profile.tryLoadAccount(db, aName); if (acc == null) { - acc = new LedgerAccount(aName); - acc.setHiddenByStar(lastAccount.isHiddenByStar()); + acc = new LedgerAccount(profile, aName); acc.setExpanded(!lastAccount.hasSubAccounts() || lastAccount.isExpanded()); } acc.setHasSubAccounts(true); - acc.removeAmounts(); // filled below when amounts are parsed - if ((!onlyStarred || !acc.isHiddenByStar()) && - acc.isVisible(accountList)) accountList.add(acc); + acc.removeAmounts(); // filled below when amounts are + // parsed + if (acc.isVisible(accountList)) + accountList.add(acc); L(String.format("gap-filling with %s", aName)); accountNames.put(aName, null); profile.storeAccount(db, acc); @@ -225,8 +263,7 @@ public class RetrieveTransactionsTask } } - if ((!onlyStarred || !lastAccount.isHiddenByStar()) && - lastAccount.isVisible(accountList)) + if (lastAccount.isVisible(accountList)) accountList.add(lastAccount); accountNames.put(acct_name, null); @@ -244,19 +281,36 @@ public class RetrieveTransactionsTask match_found = true; String value = m.group(1); String currency = m.group(2); - if (currency == null) currency = ""; - value = value.replace(',', '.'); + if (currency == null) + currency = ""; + + { + Matcher tmpM = reDecimalComma.matcher(value); + if (tmpM.find()) { + value = value.replace(".", ""); + value = value.replace(',', '.'); + } + + tmpM = reDecimalPoint.matcher(value); + if (tmpM.find()) { + value = value.replace(",", ""); + value = value.replace(" ", ""); + } + } L("curr=" + currency + ", value=" + value); final float val = Float.parseFloat(value); profile.storeAccountValue(db, lastAccount.getName(), currency, val); lastAccount.addAmount(val, currency); for (LedgerAccount syn : syntheticAccounts.values()) { + L(String.format(Locale.ENGLISH, "propagating %s %1.2f to %s", + currency, val, syn.getName())); syn.addAmount(val, currency); profile.storeAccountValue(db, syn.getName(), currency, val); } } if (match_found) { + syntheticAccounts.clear(); state = ParserState.EXPECTING_ACCOUNT; L("→ expecting account"); } @@ -264,7 +318,8 @@ public class RetrieveTransactionsTask break; case EXPECTING_TRANSACTION: - if (!line.isEmpty() && (line.charAt(0) == ' ')) continue; + if (!line.isEmpty() && (line.charAt(0) == ' ')) + continue; m = reTransactionStart.matcher(line); if (m.find()) { transactionId = Integer.valueOf(m.group(1)); @@ -288,16 +343,19 @@ public class RetrieveTransactionsTask break; case EXPECTING_TRANSACTION_DESCRIPTION: - if (!line.isEmpty() && (line.charAt(0) == ' ')) continue; + if (!line.isEmpty() && (line.charAt(0) == ' ')) + continue; m = reTransactionDescription.matcher(line); if (m.find()) { - if (transactionId == 0) throw new TransactionParserException( - "Transaction Id is 0 while expecting " + "description"); + if (transactionId == 0) + throw new TransactionParserException( + "Transaction Id is 0 while expecting " + "description"); String date = m.group(1); try { int equalsIndex = date.indexOf('='); - if (equalsIndex >= 0) date = date.substring(equalsIndex + 1); + if (equalsIndex >= 0) + date = date.substring(equalsIndex + 1); transaction = new LedgerTransaction(transactionId, date, m.group(2)); } @@ -341,27 +399,24 @@ public class RetrieveTransactionsTask // sounds like a good idea, but transaction-1 may not be the first one chronologically // for example, when you add the initial seeding transaction after entering some others // if (transactionId == 1) { -// L("This was the initial transaction. Terminating " + +// L("This was the initial transaction. +// Terminating " + // "parser"); // break LINES; // } } else { - m = reTransactionDetails.matcher(line); - if (m.find()) { - String acc_name = m.group(1); - String amount = m.group(2); - String currency = m.group(3); - if (currency == null) currency = ""; - amount = amount.replace(',', '.'); - transaction.addAccount(new LedgerTransactionAccount(acc_name, - Float.valueOf(amount), currency)); + LedgerTransactionAccount lta = parseTransactionAccountLine(line); + if (lta != null) { + transaction.addAccount(lta); L(String.format(Locale.ENGLISH, "%d: %s = %s", - transaction.getId(), acc_name, amount)); + transaction.getId(), lta.getAccountName(), + lta.getAmount())); } - else throw new IllegalStateException( - String.format("Can't parse transaction %d " + "details: %s", - transactionId, line)); + else + throw new IllegalStateException( + String.format("Can't parse transaction %d details: %s", + transactionId, line)); } break; default: @@ -423,19 +478,24 @@ public class RetrieveTransactionsTask while (true) { throwIfCancelled(); ParsedLedgerAccount parsedAccount = parser.nextAccount(); - if (parsedAccount == null) break; + if (parsedAccount == null) + break; LedgerAccount acc = profile.tryLoadAccount(db, parsedAccount.getAname()); - if (acc == null) acc = new LedgerAccount(parsedAccount.getAname()); - else acc.removeAmounts(); + if (acc == null) + acc = new LedgerAccount(profile, parsedAccount.getAname()); + else + acc.removeAmounts(); profile.storeAccount(db, acc); String lastCurrency = null; float lastCurrencyAmount = 0; for (ParsedBalance b : parsedAccount.getAibalance()) { final String currency = b.getAcommodity(); - final float amount = b.getAquantity().asFloat(); - if (currency.equals(lastCurrency)) lastCurrencyAmount += amount; + final float amount = b.getAquantity() + .asFloat(); + if (currency.equals(lastCurrency)) + lastCurrencyAmount += amount; else { if (lastCurrency != null) { profile.storeAccountValue(db, acc.getName(), lastCurrency, @@ -452,11 +512,12 @@ public class RetrieveTransactionsTask acc.addAmount(lastCurrencyAmount, lastCurrency); } - if (acc.isVisible(accountList)) accountList.add(acc); + if (acc.isVisible(accountList)) + accountList.add(acc); if (prevAccount != null) { - prevAccount.setHasSubAccounts( - acc.getName().startsWith(prevAccount.getName() + ":")); + prevAccount.setHasSubAccounts(acc.getName() + .startsWith(prevAccount.getName() + ":")); } prevAccount = acc; @@ -466,15 +527,13 @@ public class RetrieveTransactionsTask profile.deleteNotPresentAccounts(db); throwIfCancelled(); db.setTransactionSuccessful(); - listFilledOK = true; } finally { db.endTransaction(); } } - // should not be set in the DB transaction, because of a possible deadlock - // with the main and DbOpQueueRunner threads - if (listFilledOK) Data.accounts.setList(accountList); + + profile.setAccounts(accountList); return true; } @@ -515,11 +574,14 @@ public class RetrieveTransactionsTask throwIfCancelled(); ParsedLedgerTransaction parsedTransaction = parser.nextTransaction(); throwIfCancelled(); - if (parsedTransaction == null) break; + if (parsedTransaction == null) + break; LedgerTransaction transaction = parsedTransaction.asLedgerTransaction(); - if (transaction.getId() > lastTransactionId) orderAccumulator++; - else orderAccumulator--; + if (transaction.getId() > lastTransactionId) + orderAccumulator++; + else + orderAccumulator--; lastTransactionId = transaction.getId(); if (transactionOrder == DetectedTransactionOrder.UNKNOWN) { if (orderAccumulator > 30) { @@ -532,8 +594,8 @@ public class RetrieveTransactionsTask else if (orderAccumulator < -30) { transactionOrder = DetectedTransactionOrder.REVERSE_CHRONOLOGICAL; debug("rtt", String.format(Locale.ENGLISH, - "Detected reverse chronological order after %d transactions (factor %d)", - processedTransactionCount, orderAccumulator)); + "Detected reverse chronological order after %d transactions " + + "(factor %d)", processedTransactionCount, orderAccumulator)); } } @@ -619,7 +681,8 @@ public class RetrieveTransactionsTask return contextRef.get(); } private void throwIfCancelled() { - if (isCancelled()) throw new OperationCanceledException(null); + if (isCancelled()) + throw new OperationCanceledException(null); } enum DetectedTransactionOrder {UNKNOWN, REVERSE_CHRONOLOGICAL, FILE}