X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Fasync%2FRetrieveTransactionsTask.java;h=5360cd006e694acef276612afed88724e56d14c6;hp=e17b40c7c62fe69d00dc8d842f72d80a701bf880;hb=0a73337c99e2074aa7e7228204289896342ec636;hpb=ad16325ceea4984f0d383158f167af7791869477 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 e17b40c7..5360cd00 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 @@ -21,21 +21,22 @@ import android.annotation.SuppressLint; import android.database.sqlite.SQLiteDatabase; import android.os.AsyncTask; import android.os.OperationCanceledException; -import android.util.Log; +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; import net.ktnx.mobileledger.model.LedgerTransactionAccount; import net.ktnx.mobileledger.model.MobileLedgerProfile; import net.ktnx.mobileledger.ui.activity.MainActivity; -import net.ktnx.mobileledger.utils.MLDB; import net.ktnx.mobileledger.utils.NetworkUtil; import java.io.BufferedReader; @@ -50,315 +51,342 @@ import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; -import java.util.Stack; +import java.util.Locale; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RetrieveTransactionsTask extends AsyncTask { - private static final int MATCHING_TRANSACTIONS_LIMIT = 50; + 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; - private int error; // %3A is '=' private Pattern reAccountName = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\""); private Pattern reAccountValue = Pattern.compile( "\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?"); - public RetrieveTransactionsTask(WeakReference contextRef) { + private MobileLedgerProfile profile; + public RetrieveTransactionsTask(WeakReference contextRef, + @NonNull MobileLedgerProfile profile) { this.contextRef = contextRef; + this.profile = profile; } private static void L(String msg) { - //Log.d("transaction-parser", 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 = Objects.requireNonNull(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.parseFloat(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(MobileLedgerProfile profile) - throws IOException, ParseException { + private String retrieveTransactionListLegacy() throws IOException, HTTPException { Progress progress = new Progress(); int maxTransactionId = Progress.INDETERMINATE; - ArrayList accountList = new ArrayList<>(); - HashMap accountNames = new HashMap<>(); + ArrayList list = new ArrayList<>(); + HashMap map = new HashMap<>(); + ArrayList displayed = new ArrayList<>(); + ArrayList transactions = new ArrayList<>(); LedgerAccount lastAccount = null; - boolean onlyStarred = Data.optShowOnlyStarred.get(); + ArrayList syntheticAccounts = new ArrayList<>(); HttpURLConnection http = NetworkUtil.prepareConnection(profile, "journal"); http.setAllowUserInteraction(false); publishProgress(progress); - try (SQLiteDatabase db = MLDB.getWritableDatabase()) { - try (InputStream resp = http.getInputStream()) { - if (http.getResponseCode() != 200) - throw new IOException(String.format("HTTP error %d", http.getResponseCode())); - db.beginTransaction(); - try { - prepareDbForRetrieval(db, profile); - - int matchedTransactionsCount = 0; - - - ParserState state = ParserState.EXPECTING_ACCOUNT; - String line; - BufferedReader buf = - new BufferedReader(new InputStreamReader(resp, StandardCharsets.UTF_8)); - - int processedTransactionCount = 0; - int transactionId = 0; - LedgerTransaction transaction = null; - LINES: - while ((line = buf.readLine()) != null) { - throwIfCancelled(); - Matcher m; - m = reComment.matcher(line); - if (m.find()) { - // TODO: comments are ignored for now - Log.v("transaction-parser", "Ignoring comment"); + if (http.getResponseCode() != 200) + throw new HTTPException(http.getResponseCode(), http.getResponseMessage()); + + try (InputStream resp = http.getInputStream()) { + if (http.getResponseCode() != 200) + throw new IOException(String.format("HTTP error %d", http.getResponseCode())); + + int matchedTransactionsCount = 0; + + ParserState state = ParserState.EXPECTING_ACCOUNT; + String line; + BufferedReader buf = + new BufferedReader(new InputStreamReader(resp, StandardCharsets.UTF_8)); + + int processedTransactionCount = 0; + int transactionId = 0; + LedgerTransaction transaction = null; + LINES: + while ((line = buf.readLine()) != null) { + throwIfCancelled(); + Matcher m; + m = reComment.matcher(line); + if (m.find()) { + // TODO: comments are ignored for now +// Log.v("transaction-parser", "Ignoring comment"); + continue; + } + //L(String.format("State is %d", updating)); + switch (state) { + case EXPECTING_ACCOUNT: + if (line.equals("

General Journal

")) { + state = ParserState.EXPECTING_TRANSACTION; + L("→ expecting transaction"); continue; } - //L(String.format("State is %d", updating)); - switch (state) { - case EXPECTING_ACCOUNT: - if (line.equals("

General Journal

")) { - state = ParserState.EXPECTING_TRANSACTION; - L("→ expecting transaction"); - Data.accounts.set(accountList); - continue; - } - m = reAccountName.matcher(line); - if (m.find()) { - String acct_encoded = m.group(1); - String acct_name = URLDecoder.decode(acct_encoded, "UTF-8"); - acct_name = acct_name.replace("\"", ""); - L(String.format("found account: %s", acct_name)); - - lastAccount = profile.loadAccount(acct_name); - if (lastAccount == null) { - lastAccount = new LedgerAccount(acct_name); - profile.storeAccount(db, lastAccount); - } - - // make sure the parent account(s) are present, - // synthesising them if necessary - String parentName = lastAccount.getParentName(); - if (parentName != null) { - Stack toAppend = new Stack<>(); - while (parentName != null) { - if (accountNames.containsKey(parentName)) break; - toAppend.push(parentName); - parentName = - new LedgerAccount(parentName).getParentName(); - } - while (!toAppend.isEmpty()) { - String aName = toAppend.pop(); - LedgerAccount acc = new LedgerAccount(aName); - acc.setHidden(lastAccount.isHidden()); - if (!onlyStarred || !acc.isHidden()) - accountList.add(acc); - L(String.format("gap-filling with %s", aName)); - accountNames.put(aName, null); - profile.storeAccount(db, acc); - } - } - - if (!onlyStarred || !lastAccount.isHidden()) - accountList.add(lastAccount); - accountNames.put(acct_name, null); - - state = ParserState.EXPECTING_ACCOUNT_AMOUNT; - L("→ expecting account amount"); - } - break; - - case EXPECTING_ACCOUNT_AMOUNT: - m = reAccountValue.matcher(line); - boolean match_found = false; - while (m.find()) { - throwIfCancelled(); - - match_found = true; - String value = m.group(1); - String currency = m.group(2); - if (currency == null) currency = ""; + m = reAccountName.matcher(line); + if (m.find()) { + String acct_encoded = m.group(1); + String accName = URLDecoder.decode(acct_encoded, "UTF-8"); + accName = accName.replace("\"", ""); + L(String.format("found account: %s", accName)); + + lastAccount = map.get(accName); + if (lastAccount != null) { + L(String.format("ignoring duplicate account '%s'", accName)); + continue; + } + String parentAccountName = LedgerAccount.extractParentName(accName); + LedgerAccount parentAccount; + if (parentAccountName != null) { + parentAccount = ensureAccountExists(parentAccountName, map, + syntheticAccounts); + } + else { + parentAccount = null; + } + lastAccount = new LedgerAccount(profile, accName, parentAccount); + + list.add(lastAccount); + map.put(accName, lastAccount); + + state = ParserState.EXPECTING_ACCOUNT_AMOUNT; + L("→ expecting account amount"); + } + break; + + case EXPECTING_ACCOUNT_AMOUNT: + m = reAccountValue.matcher(line); + boolean match_found = false; + while (m.find()) { + throwIfCancelled(); + + match_found = true; + String value = Objects.requireNonNull(m.group(1)); + String currency = m.group(2); + if (currency == null) + currency = ""; + + { + Matcher tmpM = reDecimalComma.matcher(value); + if (tmpM.find()) { + value = value.replace(".", ""); value = value.replace(',', '.'); - L("curr=" + currency + ", value=" + value); - profile.storeAccountValue(db, lastAccount.getName(), currency, - Float.valueOf(value)); - lastAccount.addAmount(Float.parseFloat(value), currency); } - if (match_found) { - state = ParserState.EXPECTING_ACCOUNT; - L("→ expecting account"); + tmpM = reDecimalPoint.matcher(value); + if (tmpM.find()) { + value = value.replace(",", ""); + value = value.replace(" ", ""); } + } + L("curr=" + currency + ", value=" + value); + final float val = Float.parseFloat(value); + lastAccount.addAmount(val, currency); + for (LedgerAccount syn : syntheticAccounts) { + L(String.format(Locale.ENGLISH, "propagating %s %1.2f to %s", + currency, val, syn.getName())); + syn.addAmount(val, currency); + } + } - break; - - case EXPECTING_TRANSACTION: - if (!line.isEmpty() && (line.charAt(0) == ' ')) continue; - m = reTransactionStart.matcher(line); - if (m.find()) { - transactionId = Integer.valueOf(m.group(1)); - state = ParserState.EXPECTING_TRANSACTION_DESCRIPTION; - L(String.format("found transaction %d → expecting description", - transactionId)); - progress.setProgress(++processedTransactionCount); - if (maxTransactionId < transactionId) - maxTransactionId = transactionId; - if ((progress.getTotal() == Progress.INDETERMINATE) || - (progress.getTotal() < transactionId)) - progress.setTotal(transactionId); - publishProgress(progress); - } - m = reEnd.matcher(line); - if (m.find()) { - L("--- transaction value complete ---"); - break LINES; - } - break; - - case EXPECTING_TRANSACTION_DESCRIPTION: - 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"); - - String date = m.group(1); - try { - int equalsIndex = date.indexOf('='); - if (equalsIndex >= 0) - date = date.substring(equalsIndex + 1); - transaction = new LedgerTransaction(transactionId, date, - m.group(2)); - } - catch (ParseException e) { - e.printStackTrace(); - return String.format("Error parsing date '%s'", date); - } - state = ParserState.EXPECTING_TRANSACTION_DETAILS; - L(String.format("transaction %d created for %s (%s) →" + - " expecting details", transactionId, date, - m.group(2))); - } - break; - - case EXPECTING_TRANSACTION_DETAILS: - if (line.isEmpty()) { - // transaction data collected - if (transaction.existsInDb(db)) { - profile.markTransactionAsPresent(db, transaction); - matchedTransactionsCount++; - - if (matchedTransactionsCount == - MATCHING_TRANSACTIONS_LIMIT) - { - profile.markTransactionsBeforeTransactionAsPresent(db, - transaction); - progress.setTotal(progress.getProgress()); - publishProgress(progress); - break LINES; - } - } - else { - profile.storeTransaction(db, transaction); - matchedTransactionsCount = 0; - progress.setTotal(maxTransactionId); - } - - state = ParserState.EXPECTING_TRANSACTION; - L(String.format("transaction %s saved → expecting transaction", - transaction.getId())); - transaction.finishLoading(); + if (match_found) { + syntheticAccounts.clear(); + state = ParserState.EXPECTING_ACCOUNT; + L("→ expecting account"); + } + + break; + + case EXPECTING_TRANSACTION: + if (!line.isEmpty() && (line.charAt(0) == ' ')) + continue; + m = reTransactionStart.matcher(line); + if (m.find()) { + transactionId = Integer.parseInt(Objects.requireNonNull(m.group(1))); + state = ParserState.EXPECTING_TRANSACTION_DESCRIPTION; + L(String.format(Locale.ENGLISH, + "found transaction %d → expecting description", transactionId)); + progress.setProgress(++processedTransactionCount); + if (maxTransactionId < transactionId) + maxTransactionId = transactionId; + if ((progress.getTotal() == Progress.INDETERMINATE) || + (progress.getTotal() < transactionId)) + progress.setTotal(transactionId); + publishProgress(progress); + } + m = reEnd.matcher(line); + if (m.find()) { + L("--- transaction value complete ---"); + break LINES; + } + break; + + case EXPECTING_TRANSACTION_DESCRIPTION: + 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"); + + String date = Objects.requireNonNull(m.group(1)); + try { + int equalsIndex = date.indexOf('='); + if (equalsIndex >= 0) + date = date.substring(equalsIndex + 1); + transaction = + new LedgerTransaction(transactionId, date, m.group(2)); + } + catch (ParseException e) { + e.printStackTrace(); + return String.format("Error parsing date '%s'", date); + } + state = ParserState.EXPECTING_TRANSACTION_DETAILS; + L(String.format(Locale.ENGLISH, + "transaction %d created for %s (%s) →" + " expecting details", + transactionId, date, m.group(2))); + } + break; + + case EXPECTING_TRANSACTION_DETAILS: + if (line.isEmpty()) { + // transaction data collected + + transaction.finishLoading(); + transactions.add(transaction); + + state = ParserState.EXPECTING_TRANSACTION; + L(String.format("transaction %s parsed → expecting transaction", + transaction.getId())); // 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)); - L(String.format("%d: %s = %s", transaction.getId(), - acc_name, amount)); - } - else throw new IllegalStateException(String.format( - "Can't parse transaction %d " + "details: %s", - transactionId, line)); - } - break; - default: - throw new RuntimeException( - String.format("Unknown parser updating %s", state.name())); } - } - - throwIfCancelled(); + else { + LedgerTransactionAccount lta = parseTransactionAccountLine(line); + if (lta != null) { + transaction.addAccount(lta); + L(String.format(Locale.ENGLISH, "%d: %s = %s", transaction.getId(), + lta.getAccountName(), lta.getAmount())); + } + else + throw new IllegalStateException( + String.format("Can't parse transaction %d details: %s", + transactionId, line)); + } + break; + default: + throw new RuntimeException( + String.format("Unknown parser updating %s", state.name())); + } + } - profile.deleteNotPresentTransactions(db); - db.setTransactionSuccessful(); + throwIfCancelled(); - profile.setLastUpdateStamp(); + profile.setAndStoreAccountAndTransactionListFromWeb(list, transactions); - return null; - } - finally { - db.endTransaction(); - } - } + return null; } } - private void prepareDbForRetrieval(SQLiteDatabase db, MobileLedgerProfile profile) { - db.execSQL("UPDATE transactions set keep=0 where profile=?", - new String[]{profile.getUuid()}); - db.execSQL("update account_values set keep=0 where profile=?;", - new String[]{profile.getUuid()}); - db.execSQL("update accounts set keep=0 where profile=?;", new String[]{profile.getUuid()}); + private @NonNull + LedgerAccount ensureAccountExists(String accountName, HashMap map, + ArrayList createdAccounts) { + LedgerAccount acc = map.get(accountName); + + if (acc != null) + return acc; + + String parentName = LedgerAccount.extractParentName(accountName); + LedgerAccount parentAccount; + if (parentName != null) { + parentAccount = ensureAccountExists(parentName, map, createdAccounts); + } + else { + parentAccount = null; + } + + acc = new LedgerAccount(profile, accountName, parentAccount); + createdAccounts.add(acc); + return acc; } - private boolean retrieveAccountList(MobileLedgerProfile profile) - throws IOException, HTTPException { + private boolean retrieveAccountList() throws IOException, HTTPException { Progress progress = new Progress(); HttpURLConnection http = NetworkUtil.prepareConnection(profile, "accounts"); @@ -372,125 +400,134 @@ public class RetrieveTransactionsTask throw new HTTPException(http.getResponseCode(), http.getResponseMessage()); } publishProgress(progress); - try (SQLiteDatabase db = MLDB.getWritableDatabase()) { - try (InputStream resp = http.getInputStream()) { - if (http.getResponseCode() != 200) - throw new IOException(String.format("HTTP error %d", http.getResponseCode())); - - db.beginTransaction(); - try { - profile.markAccountsAsNotPresent(db); - - AccountListParser parser = new AccountListParser(resp); - ArrayList accountList = new ArrayList<>(); - - while (true) { - throwIfCancelled(); - ParsedLedgerAccount parsedAccount = parser.nextAccount(); - if (parsedAccount == null) break; - - LedgerAccount acc = new LedgerAccount(parsedAccount.getAname()); - profile.storeAccount(db, acc); - for (ParsedBalance b : parsedAccount.getAebalance()) { - profile.storeAccountValue(db, acc.getName(), b.getAcommodity(), - b.getAquantity().asFloat()); - } + SQLiteDatabase db = App.getDatabase(); + ArrayList list = new ArrayList<>(); + HashMap map = new HashMap<>(); + HashMap currentMap = new HashMap<>(); + for (LedgerAccount acc : Objects.requireNonNull(profile.getAllAccounts())) + currentMap.put(acc.getName(), acc); + try (InputStream resp = http.getInputStream()) { + if (http.getResponseCode() != 200) + throw new IOException(String.format("HTTP error %d", http.getResponseCode())); + + AccountListParser parser = new AccountListParser(resp); + + while (true) { + throwIfCancelled(); + ParsedLedgerAccount parsedAccount = parser.nextAccount(); + if (parsedAccount == null) { + break; + } - accountList.add(acc); - } - throwIfCancelled(); + final String accName = parsedAccount.getAname(); + LedgerAccount acc = map.get(accName); + if (acc != null) + throw new RuntimeException( + String.format("Account '%s' already present", acc.getName())); + String parentName = LedgerAccount.extractParentName(accName); + ArrayList createdParents = new ArrayList<>(); + LedgerAccount parent; + if (parentName == null) { + parent = null; + } + else { + parent = ensureAccountExists(parentName, map, createdParents); + parent.setHasSubAccounts(true); + } + acc = new LedgerAccount(profile, accName, parent); + list.add(acc); + map.put(accName, acc); - profile.deleteNotPresentAccounts(db); + String lastCurrency = null; + float lastCurrencyAmount = 0; + for (ParsedBalance b : parsedAccount.getAibalance()) { throwIfCancelled(); - db.setTransactionSuccessful(); - Data.accounts.set(accountList); + final String currency = b.getAcommodity(); + final float amount = b.getAquantity() + .asFloat(); + if (currency.equals(lastCurrency)) { + lastCurrencyAmount += amount; + } + else { + if (lastCurrency != null) { + acc.addAmount(lastCurrencyAmount, lastCurrency); + } + lastCurrency = currency; + lastCurrencyAmount = amount; + } } - finally { - db.endTransaction(); + if (lastCurrency != null) { + acc.addAmount(lastCurrencyAmount, lastCurrency); } + for (LedgerAccount p : createdParents) + acc.propagateAmountsTo(p); } + throwIfCancelled(); } + // the current account tree may have changed, update the new-to be tree to match + for (LedgerAccount acc : list) { + LedgerAccount prevData = currentMap.get(acc.getName()); + if (prevData != null) { + acc.setExpanded(prevData.isExpanded()); + acc.setAmountsExpanded(prevData.amountsExpanded()); + } + } + + profile.setAndStoreAccountListFromWeb(list); return true; } - private boolean retrieveTransactionList(MobileLedgerProfile profile) - throws IOException, ParseException, HTTPException { + private boolean retrieveTransactionList() throws IOException, ParseException, HTTPException { Progress progress = new Progress(); - int maxTransactionId = Progress.INDETERMINATE; + int maxTransactionId = Data.transactions.size(); HttpURLConnection http = NetworkUtil.prepareConnection(profile, "transactions"); http.setAllowUserInteraction(false); publishProgress(progress); switch (http.getResponseCode()) { - case 200: break; - case 404: return false; - default: throw new HTTPException(http.getResponseCode(), http.getResponseMessage()); + case 200: + break; + case 404: + return false; + default: + throw new HTTPException(http.getResponseCode(), http.getResponseMessage()); } - try (SQLiteDatabase db = MLDB.getWritableDatabase()) { - try (InputStream resp = http.getInputStream()) { - if (http.getResponseCode() != 200) - throw new IOException(String.format("HTTP error %d", http.getResponseCode())); + try (InputStream resp = http.getInputStream()) { + throwIfCancelled(); + ArrayList trList = new ArrayList<>(); + + TransactionListParser parser = new TransactionListParser(resp); + + int processedTransactionCount = 0; + + while (true) { throwIfCancelled(); - db.beginTransaction(); - try { - profile.markTransactionsAsNotPresent(db); - - int matchedTransactionsCount = 0; - TransactionListParser parser = new TransactionListParser(resp); - - int processedTransactionCount = 0; - - while (true) { - throwIfCancelled(); - ParsedLedgerTransaction parsedTransaction = parser.nextTransaction(); - throwIfCancelled(); - if (parsedTransaction == null) break; - LedgerTransaction transaction = parsedTransaction.asLedgerTransaction(); - if (transaction.existsInDb(db)) { - profile.markTransactionAsPresent(db, transaction); - matchedTransactionsCount++; - - if (matchedTransactionsCount == MATCHING_TRANSACTIONS_LIMIT) { - profile.markTransactionsBeforeTransactionAsPresent(db, transaction); - progress.setTotal(progress.getProgress()); - publishProgress(progress); - db.setTransactionSuccessful(); - profile.setLastUpdateStamp(); - return true; - } - } - else { - profile.storeTransaction(db, transaction); - matchedTransactionsCount = 0; - progress.setTotal(maxTransactionId); - } + ParsedLedgerTransaction parsedTransaction = parser.nextTransaction(); + throwIfCancelled(); + if (parsedTransaction == null) + break; - progress.setProgress(++processedTransactionCount); - publishProgress(progress); - } + LedgerTransaction transaction = parsedTransaction.asLedgerTransaction(); + trList.add(transaction); - throwIfCancelled(); - profile.deleteNotPresentTransactions(db); - throwIfCancelled(); - db.setTransactionSuccessful(); - profile.setLastUpdateStamp(); - } - finally { - db.endTransaction(); - } + progress.setProgress(++processedTransactionCount); + publishProgress(progress); } + + throwIfCancelled(); + profile.setAndStoreTransactionList(trList); } return true; } + @SuppressLint("DefaultLocale") @Override protected String doInBackground(Void... params) { - MobileLedgerProfile profile = Data.profile.get(); - Data.backgroundTaskCount.incrementAndGet(); + Data.backgroundTaskStarted(); try { - if (!retrieveAccountList(profile) || !retrieveTransactionList(profile)) - return retrieveTransactionListLegacy(profile); + if (!retrieveAccountList() || !retrieveTransactionList()) + return retrieveTransactionListLegacy(); return null; } catch (MalformedURLException e) { @@ -503,7 +540,7 @@ public class RetrieveTransactionsTask } catch (IOException e) { e.printStackTrace(); - return "Parse error"; + return e.getLocalizedMessage(); } catch (ParseException e) { e.printStackTrace(); @@ -514,22 +551,22 @@ public class RetrieveTransactionsTask return "Operation cancelled"; } finally { - Data.backgroundTaskCount.decrementAndGet(); + Data.backgroundTaskFinished(); } } private MainActivity getContext() { return contextRef.get(); } private void throwIfCancelled() { - if (isCancelled()) throw new OperationCanceledException(null); + if (isCancelled()) + throw new OperationCanceledException(null); } - private enum ParserState { - EXPECTING_ACCOUNT, EXPECTING_ACCOUNT_AMOUNT, EXPECTING_JOURNAL, EXPECTING_TRANSACTION, + EXPECTING_ACCOUNT, EXPECTING_ACCOUNT_AMOUNT, EXPECTING_TRANSACTION, EXPECTING_TRANSACTION_DESCRIPTION, EXPECTING_TRANSACTION_DETAILS } - public class Progress { + public static class Progress { public static final int INDETERMINATE = -1; private int progress; private int total; @@ -554,7 +591,7 @@ public class RetrieveTransactionsTask } } - private class TransactionParserException extends IllegalStateException { + private static class TransactionParserException extends IllegalStateException { TransactionParserException(String message) { super(message); }