X-Git-Url: https://git.ktnx.net/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Fasync%2FRetrieveTransactionsTask.java;h=0b751b4c6d7df0c94174973ec7a2e449352c9dc9;hb=HEAD;hp=d092a1cbfc55f1f0294cef1f6fc583248e14efa6;hpb=a87079ed41bdc3ad89fe8bd15dfba10e37b29b76;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 d092a1cb..0b751b4c 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 © 2020 Damyan Ivanov. + * Copyright © 2021 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 @@ -18,16 +18,23 @@ package net.ktnx.mobileledger.async; import android.annotation.SuppressLint; -import android.database.sqlite.SQLiteDatabase; -import android.os.AsyncTask; import android.os.OperationCanceledException; import androidx.annotation.NonNull; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.RuntimeJsonMappingException; -import net.ktnx.mobileledger.App; +import net.ktnx.mobileledger.dao.AccountDAO; +import net.ktnx.mobileledger.dao.TransactionDAO; +import net.ktnx.mobileledger.db.Account; +import net.ktnx.mobileledger.db.AccountWithAmounts; +import net.ktnx.mobileledger.db.DB; +import net.ktnx.mobileledger.db.Option; +import net.ktnx.mobileledger.db.Profile; +import net.ktnx.mobileledger.db.TransactionWithAccounts; import net.ktnx.mobileledger.err.HTTPException; +import net.ktnx.mobileledger.json.API; import net.ktnx.mobileledger.json.AccountListParser; import net.ktnx.mobileledger.json.ApiNotSupportedException; import net.ktnx.mobileledger.json.TransactionListParser; @@ -35,8 +42,6 @@ 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.MainModel; import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.NetworkUtil; @@ -51,6 +56,7 @@ import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -59,8 +65,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class RetrieveTransactionsTask extends - AsyncTask { +public class RetrieveTransactionsTask extends Thread { private static final int MATCHING_TRANSACTIONS_LIMIT = 150; private static final Pattern reComment = Pattern.compile("^\\s*;"); private static final Pattern reTransactionStart = Pattern.compile( @@ -74,21 +79,16 @@ public class RetrieveTransactionsTask extends private static final Pattern reEnd = Pattern.compile("\\bid=\"addmodal\""); private static final Pattern reDecimalPoint = Pattern.compile("\\.\\d\\d?$"); private static final Pattern reDecimalComma = Pattern.compile(",\\d\\d?$"); + private static final String TAG = "RTT"; // %3A is '=' private final Pattern reAccountName = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\""); private final Pattern reAccountValue = Pattern.compile( "\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?"); - private final MainModel mainModel; - private final MobileLedgerProfile profile; - private final List prevAccounts; + private final Profile profile; private int expectedPostingsCount = -1; - public RetrieveTransactionsTask(@NonNull MainModel mainModel, - @NonNull MobileLedgerProfile profile, - List accounts) { - this.mainModel = mainModel; + public RetrieveTransactionsTask(@NonNull Profile profile) { this.profile = profile; - this.prevAccounts = accounts; } private static void L(String msg) { //debug("transaction-parser", msg); @@ -120,28 +120,19 @@ public class RetrieveTransactionsTask extends return null; } } - public MobileLedgerProfile getProfile() { - return profile; + private void publishProgress(Progress progress) { + Data.backgroundTaskProgress.postValue(progress); } - @Override - protected void onProgressUpdate(Progress... values) { - super.onProgressUpdate(values); - Data.backgroundTaskProgress.postValue(values[0]); - } - @Override - protected void onPostExecute(Result result) { - super.onPostExecute(result); + private void finish(Result result) { Progress progress = new Progress(); progress.setState(ProgressState.FINISHED); progress.setError(result.error); - onProgressUpdate(progress); + publishProgress(progress); } - @Override - protected void onCancelled() { - super.onCancelled(); + private void cancel() { Progress progress = new Progress(); progress.setState(ProgressState.FINISHED); - onProgressUpdate(progress); + publishProgress(progress); } private void retrieveTransactionListLegacy(List accounts, List transactions) @@ -213,7 +204,7 @@ public class RetrieveTransactionsTask extends else { parentAccount = null; } - lastAccount = new LedgerAccount(profile, accName, parentAccount); + lastAccount = new LedgerAccount(accName, parentAccount); accounts.add(lastAccount); map.put(accName, lastAccount); @@ -327,7 +318,7 @@ public class RetrieveTransactionsTask extends state = ParserState.EXPECTING_TRANSACTION; L(String.format("transaction %s parsed → expecting transaction", - transaction.getId())); + transaction.getLedgerId())); // 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 @@ -342,8 +333,9 @@ public class RetrieveTransactionsTask extends LedgerTransactionAccount lta = parseTransactionAccountLine(line); if (lta != null) { transaction.addAccount(lta); - L(String.format(Locale.ENGLISH, "%d: %s = %s", transaction.getId(), - lta.getAccountName(), lta.getAmount())); + L(String.format(Locale.ENGLISH, "%d: %s = %s", + transaction.getLedgerId(), lta.getAccountName(), + lta.getAmount())); } else throw new IllegalStateException( @@ -377,7 +369,7 @@ public class RetrieveTransactionsTask extends parentAccount = null; } - acc = new LedgerAccount(profile, accountName, parentAccount); + acc = new LedgerAccount(accountName, parentAccount); createdAccounts.add(acc); return acc; } @@ -386,11 +378,11 @@ public class RetrieveTransactionsTask extends } private List retrieveAccountList() throws IOException, HTTPException, ApiNotSupportedException { - final SendTransactionTask.API apiVersion = profile.getApiVersion(); - if (apiVersion.equals(SendTransactionTask.API.auto)) { + final API apiVersion = API.valueOf(profile.getApiVersion()); + if (apiVersion.equals(API.auto)) { return retrieveAccountListAnyVersion(); } - else if (apiVersion.equals(SendTransactionTask.API.html)) { + else if (apiVersion.equals(API.html)) { Logger.debug("json", "Declining using JSON API for /accounts with configured legacy API version"); return null; @@ -400,23 +392,22 @@ public class RetrieveTransactionsTask extends } } private List retrieveAccountListAnyVersion() - throws HTTPException, ApiNotSupportedException { - for (SendTransactionTask.API ver : SendTransactionTask.API.allVersions) { + throws ApiNotSupportedException, IOException, HTTPException { + for (API ver : API.allVersions) { try { return retrieveAccountListForVersion(ver); } - catch (Exception e) { + catch (JsonParseException | RuntimeJsonMappingException e) { Logger.debug("json", String.format(Locale.US, "Error during account list retrieval using API %s", - ver.getDescription())); + ver.getDescription()), e); } - throw new ApiNotSupportedException(); } - throw new RuntimeException("This should never be reached"); + throw new ApiNotSupportedException(); } - private List retrieveAccountListForVersion(SendTransactionTask.API version) + private List retrieveAccountListForVersion(API version) throws IOException, HTTPException { HttpURLConnection http = NetworkUtil.prepareConnection(profile, "accounts"); http.setAllowUserInteraction(false); @@ -429,12 +420,8 @@ public class RetrieveTransactionsTask extends throw new HTTPException(http.getResponseCode(), http.getResponseMessage()); } publishProgress(Progress.indeterminate()); - SQLiteDatabase db = App.getDatabase(); ArrayList list = new ArrayList<>(); HashMap map = new HashMap<>(); - HashMap currentMap = new HashMap<>(); - for (LedgerAccount acc : prevAccounts) - currentMap.put(acc.getName(), acc); throwIfCancelled(); try (InputStream resp = http.getInputStream()) { throwIfCancelled(); @@ -452,26 +439,21 @@ public class RetrieveTransactionsTask extends list.add(acc); } 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()); - } + Logger.warn("accounts", + String.format(Locale.US, "Got %d accounts using protocol %s", list.size(), + version.getDescription())); } return list; } private List retrieveTransactionList() throws ParseException, HTTPException, IOException, ApiNotSupportedException { - final SendTransactionTask.API apiVersion = profile.getApiVersion(); - if (apiVersion.equals(SendTransactionTask.API.auto)) { + final API apiVersion = API.valueOf(profile.getApiVersion()); + if (apiVersion.equals(API.auto)) { return retrieveTransactionListAnyVersion(); } - else if (apiVersion.equals(SendTransactionTask.API.html)) { + else if (apiVersion.equals(API.html)) { Logger.debug("json", "Declining using JSON API for /accounts with configured legacy API version"); return null; @@ -483,23 +465,22 @@ public class RetrieveTransactionsTask extends } private List retrieveTransactionListAnyVersion() throws ApiNotSupportedException { - for (SendTransactionTask.API ver : SendTransactionTask.API.allVersions) { + for (API ver : API.allVersions) { try { return retrieveTransactionListForVersion(ver); } - catch (Exception | HTTPException e) { - Logger.debug("json", - String.format(Locale.US, "Error during account list retrieval using API %s", - ver.getDescription())); + catch (Exception e) { + Logger.debug("json", String.format(Locale.US, + "Error during transaction list retrieval using API %s", + ver.getDescription()), e); } - throw new ApiNotSupportedException(); } - throw new RuntimeException("This should never be reached"); + throw new ApiNotSupportedException(); } - private List retrieveTransactionListForVersion( - SendTransactionTask.API apiVersion) throws IOException, ParseException, HTTPException { + private List retrieveTransactionListForVersion(API apiVersion) + throws IOException, ParseException, HTTPException { Progress progress = new Progress(); progress.setTotal(expectedPostingsCount); @@ -545,23 +526,27 @@ public class RetrieveTransactionsTask extends } throwIfCancelled(); + + Logger.warn("transactions", + String.format(Locale.US, "Got %d transactions using protocol %s", trList.size(), + apiVersion.getDescription())); } - // json interface returns transactions if file order and the rest of the machinery + // json interface returns transactions in file order and the rest of the machinery // expects them in reverse chronological order Collections.sort(trList, (o1, o2) -> { int res = o2.getDate() .compareTo(o1.getDate()); if (res != 0) return res; - return Integer.compare(o2.getId(), o1.getId()); + return Long.compare(o2.getLedgerId(), o1.getLedgerId()); }); return trList; } @SuppressLint("DefaultLocale") @Override - protected Result doInBackground(Void... params) { + public void run() { Data.backgroundTaskStarted(); List accounts; List transactions; @@ -581,45 +566,48 @@ public class RetrieveTransactionsTask extends transactions = new ArrayList<>(); retrieveTransactionListLegacy(accounts, transactions); } - mainModel.setAndStoreAccountAndTransactionListFromWeb(accounts, transactions); - return new Result(accounts, transactions); + new AccountAndTransactionListSaver(accounts, transactions).start(); + + Data.lastUpdateDate.postValue(new Date()); + + finish(new Result(null)); } catch (MalformedURLException e) { e.printStackTrace(); - return new Result("Invalid server URL"); + finish(new Result("Invalid server URL")); } catch (HTTPException e) { e.printStackTrace(); - return new Result(String.format("HTTP error %d: %s", e.getResponseCode(), - e.getResponseMessage())); + finish(new Result( + String.format("HTTP error %d: %s", e.getResponseCode(), e.getMessage()))); } catch (IOException e) { e.printStackTrace(); - return new Result(e.getLocalizedMessage()); + finish(new Result(e.getLocalizedMessage())); } catch (RuntimeJsonMappingException e) { e.printStackTrace(); - return new Result(Result.ERR_JSON_PARSER_ERROR); + finish(new Result(Result.ERR_JSON_PARSER_ERROR)); } catch (ParseException e) { e.printStackTrace(); - return new Result("Network error"); + finish(new Result("Network error")); } catch (OperationCanceledException e) { - e.printStackTrace(); - return new Result("Operation cancelled"); + Logger.debug("RTT", "Retrieval was cancelled", e); + finish(new Result(null)); } catch (ApiNotSupportedException e) { e.printStackTrace(); - return new Result("Server version not supported"); + finish(new Result("Server version not supported")); } finally { Data.backgroundTaskFinished(); } } public void throwIfCancelled() { - if (isCancelled()) + if (isInterrupted()) throw new OperationCanceledException(null); } private enum ParserState { @@ -715,4 +703,55 @@ public class RetrieveTransactionsTask extends this.transactions = transactions; } } + + private class AccountAndTransactionListSaver extends Thread { + private final List accounts; + private final List transactions; + public AccountAndTransactionListSaver(List accounts, + List transactions) { + this.accounts = accounts; + this.transactions = transactions; + } + @Override + public void run() { + AccountDAO accDao = DB.get() + .getAccountDAO(); + TransactionDAO trDao = DB.get() + .getTransactionDAO(); + + Logger.debug(TAG, "Preparing account list"); + final List list = new ArrayList<>(); + for (LedgerAccount acc : accounts) { + final AccountWithAmounts a = acc.toDBOWithAmounts(); + Account existing = accDao.getByNameSync(profile.getId(), acc.getName()); + if (existing != null) { + a.account.setExpanded(existing.isExpanded()); + a.account.setAmountsExpanded(existing.isAmountsExpanded()); + a.account.setId(existing.getId()); // not strictly needed, but since we have it + // anyway... + } + + list.add(a); + } + Logger.debug(TAG, "Account list prepared. Storing"); + accDao.storeAccountsSync(list, profile.getId()); + Logger.debug(TAG, "Account list stored"); + + Logger.debug(TAG, "Preparing transaction list"); + final List tranList = new ArrayList<>(); + + for (LedgerTransaction tr : transactions) + tranList.add(tr.toDBO()); + + Logger.debug(TAG, "Storing transaction list"); + trDao.storeTransactionsSync(tranList, profile.getId()); + + Logger.debug(TAG, "Transactions stored"); + + DB.get() + .getOptionDAO() + .insertSync(new Option(profile.getId(), Option.OPT_LAST_SCRAPE, + String.valueOf((new Date()).getTime()))); + } + } }