X-Git-Url: https://git.ktnx.net/?p=mobile-ledger-staging.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Fasync%2FRetrieveTransactionsTask.java;h=1d69fa2941a1f0963f5857c560f15bf244e2d751;hp=5360cd006e694acef276612afed88724e56d14c6;hb=20c03b7a5eb152d42fbbe9ecbaae27530563b398;hpb=0a73337c99e2074aa7e7228204289896342ec636 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 5360cd00..1d69fa29 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java @@ -36,29 +36,30 @@ 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.ui.MainModel; import net.ktnx.mobileledger.utils.NetworkUtil; import java.io.BufferedReader; 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.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class RetrieveTransactionsTask - extends AsyncTask { +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( @@ -72,16 +73,21 @@ public class RetrieveTransactionsTask 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 WeakReference contextRef; // %3A is '=' - private Pattern reAccountName = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\""); - private Pattern reAccountValue = Pattern.compile( + 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 MobileLedgerProfile profile; - public RetrieveTransactionsTask(WeakReference contextRef, - @NonNull MobileLedgerProfile profile) { - this.contextRef = contextRef; + private final MainModel mainModel; + private final MobileLedgerProfile profile; + private final List prevAccounts; + private int expectedPostingsCount = -1; + public RetrieveTransactionsTask(@NonNull MainModel mainModel, + @NonNull MobileLedgerProfile profile, + List accounts) { + this.mainModel = mainModel; this.profile = profile; + this.prevAccounts = accounts; } private static void L(String msg) { //debug("transaction-parser", msg); @@ -116,42 +122,31 @@ public class RetrieveTransactionsTask @Override protected void onProgressUpdate(Progress... values) { super.onProgressUpdate(values); - MainActivity context = getContext(); - if (context == null) - return; - context.onRetrieveProgress(values[0]); + Data.backgroundTaskProgress.postValue(values[0]); } @Override - protected void onPreExecute() { - super.onPreExecute(); - MainActivity context = getContext(); - if (context == null) - return; - context.onRetrieveStart(); - } - @Override - protected void onPostExecute(String error) { - super.onPostExecute(error); - MainActivity context = getContext(); - if (context == null) - return; - context.onRetrieveDone(error); + protected void onPostExecute(Result result) { + super.onPostExecute(result); + Progress progress = new Progress(); + progress.setState(ProgressState.FINISHED); + progress.setError(result.error); + onProgressUpdate(progress); } @Override protected void onCancelled() { super.onCancelled(); - MainActivity context = getContext(); - if (context == null) - return; - context.onRetrieveDone(null); - } - private String retrieveTransactionListLegacy() throws IOException, HTTPException { Progress progress = new Progress(); - int maxTransactionId = Progress.INDETERMINATE; - ArrayList list = new ArrayList<>(); + progress.setState(ProgressState.FINISHED); + onProgressUpdate(progress); + } + private void retrieveTransactionListLegacy(List accounts, + List transactions) + throws IOException, HTTPException { + Progress progress = Progress.indeterminate(); + progress.setState(ProgressState.RUNNING); + progress.setTotal(expectedPostingsCount); + int maxTransactionId = -1; HashMap map = new HashMap<>(); - ArrayList displayed = new ArrayList<>(); - ArrayList transactions = new ArrayList<>(); LedgerAccount lastAccount = null; ArrayList syntheticAccounts = new ArrayList<>(); @@ -216,7 +211,7 @@ public class RetrieveTransactionsTask } lastAccount = new LedgerAccount(profile, accName, parentAccount); - list.add(lastAccount); + accounts.add(lastAccount); map.put(accName, lastAccount); state = ParserState.EXPECTING_ACCOUNT_AMOUNT; @@ -279,7 +274,7 @@ public class RetrieveTransactionsTask progress.setProgress(++processedTransactionCount); if (maxTransactionId < transactionId) maxTransactionId = transactionId; - if ((progress.getTotal() == Progress.INDETERMINATE) || + if ((progress.isIndeterminate()) || (progress.getTotal() < transactionId)) progress.setTotal(transactionId); publishProgress(progress); @@ -298,7 +293,7 @@ public class RetrieveTransactionsTask if (m.find()) { if (transactionId == 0) throw new TransactionParserException( - "Transaction Id is 0 while expecting " + "description"); + "Transaction Id is 0 while expecting description"); String date = Objects.requireNonNull(m.group(1)); try { @@ -309,8 +304,8 @@ public class RetrieveTransactionsTask new LedgerTransaction(transactionId, date, m.group(2)); } catch (ParseException e) { - e.printStackTrace(); - return String.format("Error parsing date '%s'", date); + throw new TransactionParserException( + String.format("Error parsing date '%s'", date)); } state = ParserState.EXPECTING_TRANSACTION_DETAILS; L(String.format(Locale.ENGLISH, @@ -359,10 +354,6 @@ public class RetrieveTransactionsTask } throwIfCancelled(); - - profile.setAndStoreAccountAndTransactionListFromWeb(list, transactions); - - return null; } } private @NonNull @@ -386,31 +377,32 @@ public class RetrieveTransactionsTask createdAccounts.add(acc); return acc; } - private boolean retrieveAccountList() throws IOException, HTTPException { - Progress progress = new Progress(); - + private List retrieveAccountList() throws IOException, HTTPException { HttpURLConnection http = NetworkUtil.prepareConnection(profile, "accounts"); http.setAllowUserInteraction(false); switch (http.getResponseCode()) { case 200: break; case 404: - return false; + return null; default: throw new HTTPException(http.getResponseCode(), http.getResponseMessage()); } - publishProgress(progress); + publishProgress(Progress.indeterminate()); SQLiteDatabase db = App.getDatabase(); ArrayList list = new ArrayList<>(); HashMap map = new HashMap<>(); HashMap currentMap = new HashMap<>(); - for (LedgerAccount acc : Objects.requireNonNull(profile.getAllAccounts())) + for (LedgerAccount acc : prevAccounts) currentMap.put(acc.getName(), acc); + throwIfCancelled(); try (InputStream resp = http.getInputStream()) { + throwIfCancelled(); if (http.getResponseCode() != 200) throw new IOException(String.format("HTTP error %d", http.getResponseCode())); AccountListParser parser = new AccountListParser(resp); + expectedPostingsCount = 0; while (true) { throwIfCancelled(); @@ -418,7 +410,7 @@ public class RetrieveTransactionsTask if (parsedAccount == null) { break; } - + expectedPostingsCount += parsedAccount.getAnumpostings(); final String accName = parsedAccount.getAname(); LedgerAccount acc = map.get(accName); if (acc != null) @@ -474,12 +466,12 @@ public class RetrieveTransactionsTask } } - profile.setAndStoreAccountListFromWeb(list); - return true; + return list; } - private boolean retrieveTransactionList() throws IOException, ParseException, HTTPException { + private List retrieveTransactionList() + throws IOException, ParseException, HTTPException { Progress progress = new Progress(); - int maxTransactionId = Data.transactions.size(); + progress.setTotal(expectedPostingsCount); HttpURLConnection http = NetworkUtil.prepareConnection(profile, "transactions"); http.setAllowUserInteraction(false); @@ -488,17 +480,17 @@ public class RetrieveTransactionsTask case 200: break; case 404: - return false; + return null; default: throw new HTTPException(http.getResponseCode(), http.getResponseMessage()); } + ArrayList trList = new ArrayList<>(); try (InputStream resp = http.getInputStream()) { throwIfCancelled(); - ArrayList trList = new ArrayList<>(); TransactionListParser parser = new TransactionListParser(resp); - int processedTransactionCount = 0; + int processedPostings = 0; while (true) { throwIfCancelled(); @@ -510,53 +502,80 @@ public class RetrieveTransactionsTask LedgerTransaction transaction = parsedTransaction.asLedgerTransaction(); trList.add(transaction); - progress.setProgress(++processedTransactionCount); + progress.setProgress(processedPostings += transaction.getAccounts() + .size()); +// Logger.debug("trParser", +// String.format(Locale.US, "Parsed transaction %d - %s", transaction +// .getId(), +// transaction.getDescription())); +// for (LedgerTransactionAccount acc : transaction.getAccounts()) { +// Logger.debug("trParser", +// String.format(Locale.US, " %s", acc.getAccountName())); +// } publishProgress(progress); } throwIfCancelled(); - profile.setAndStoreTransactionList(trList); } - return true; + // json interface returns transactions if 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 trList; } @SuppressLint("DefaultLocale") @Override - protected String doInBackground(Void... params) { + protected Result doInBackground(Void... params) { Data.backgroundTaskStarted(); + List accounts; + List transactions; try { - if (!retrieveAccountList() || !retrieveTransactionList()) - return retrieveTransactionListLegacy(); - return null; + accounts = retrieveAccountList(); + if (accounts == null) + transactions = null; + else + transactions = retrieveTransactionList(); + if (accounts == null || transactions == null) { + accounts = new ArrayList<>(); + transactions = new ArrayList<>(); + retrieveTransactionListLegacy(accounts, transactions); + } + mainModel.setAndStoreAccountAndTransactionListFromWeb(accounts, transactions); + + return new Result(accounts, transactions); } catch (MalformedURLException e) { e.printStackTrace(); - return "Invalid server URL"; + return new Result("Invalid server URL"); } catch (HTTPException e) { e.printStackTrace(); - return String.format("HTTP error %d: %s", e.getResponseCode(), e.getResponseMessage()); + return new Result(String.format("HTTP error %d: %s", e.getResponseCode(), + e.getResponseMessage())); } catch (IOException e) { e.printStackTrace(); - return e.getLocalizedMessage(); + return new Result(e.getLocalizedMessage()); } catch (ParseException e) { e.printStackTrace(); - return "Network error"; + return new Result("Network error"); } catch (OperationCanceledException e) { e.printStackTrace(); - return "Operation cancelled"; + return new Result("Operation cancelled"); } finally { Data.backgroundTaskFinished(); } } - private MainActivity getContext() { - return contextRef.get(); - } private void throwIfCancelled() { if (isCancelled()) throw new OperationCanceledException(null); @@ -566,28 +585,72 @@ public class RetrieveTransactionsTask EXPECTING_TRANSACTION_DESCRIPTION, EXPECTING_TRANSACTION_DETAILS } + public enum ProgressState {STARTING, RUNNING, FINISHED} + public static class Progress { - public static final int INDETERMINATE = -1; private int progress; private int total; + private ProgressState state = ProgressState.RUNNING; + private String error = null; + private boolean indeterminate; Progress() { - this(INDETERMINATE, INDETERMINATE); + indeterminate = true; } Progress(int progress, int total) { + this.indeterminate = false; this.progress = progress; this.total = total; } + public static Progress indeterminate() { + return new Progress(); + } + public static Progress finished(String error) { + Progress p = new Progress(); + p.setState(ProgressState.FINISHED); + p.setError(error); + return p; + } public int getProgress() { + ensureState(ProgressState.RUNNING); return progress; } protected void setProgress(int progress) { this.progress = progress; + this.state = ProgressState.RUNNING; } public int getTotal() { + ensureState(ProgressState.RUNNING); return total; } protected void setTotal(int total) { this.total = total; + state = ProgressState.RUNNING; + indeterminate = total == -1; + } + private void ensureState(ProgressState wanted) { + if (state != wanted) + throw new IllegalStateException( + String.format("Bad state: %s, expected %s", state, wanted)); + } + public ProgressState getState() { + return state; + } + public void setState(ProgressState state) { + this.state = state; + } + public String getError() { + ensureState(ProgressState.FINISHED); + return error; + } + public void setError(String error) { + this.error = error; + state = ProgressState.FINISHED; + } + public boolean isIndeterminate() { + return indeterminate; + } + public void setIndeterminate(boolean indeterminate) { + this.indeterminate = indeterminate; } } @@ -596,4 +659,17 @@ public class RetrieveTransactionsTask super(message); } } + + public static class Result { + public String error; + public List accounts; + public List transactions; + Result(String error) { + this.error = error; + } + Result(List accounts, List transactions) { + this.accounts = accounts; + this.transactions = transactions; + } + } }