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=7b898593870280e2b1302dff63ce4c44f95c3e0e;hp=57643f5e711fba01eaba29aa943cf5eec2eb947b;hb=7b2966220b258cc2f1becae3ff91ef62a36cc01b;hpb=b5a5d8a0367d42a72f5db81ef9f82b2973f67fcc 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 57643f5e..7b898593 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java @@ -18,15 +18,18 @@ package net.ktnx.mobileledger.async; import android.annotation.SuppressLint; -import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.os.AsyncTask; +import android.os.OperationCanceledException; import android.util.Log; import net.ktnx.mobileledger.R; +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.ui.transaction_list.TransactionListFragment; +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; @@ -38,69 +41,84 @@ import java.io.InputStreamReader; import java.lang.ref.WeakReference; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.URLDecoder; +import java.util.ArrayList; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class RetrieveTransactionsTask extends - AsyncTask { +public class RetrieveTransactionsTask + extends AsyncTask { + public static final int MATCHING_TRANSACTIONS_LIMIT = 50; + public static final Pattern commentPattern = Pattern.compile("^\\s*;"); private static final Pattern transactionStartPattern = Pattern.compile("([\\d.-]+)"); + "id=\"transaction-(\\d+)\">([\\d.-]+)"); private static final Pattern transactionDescriptionPattern = Pattern.compile(" contextRef; + protected WeakReference contextRef; protected int error; + Pattern account_name_re = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\""); + Pattern account_value_re = Pattern.compile( + "\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?"); + Pattern tr_end_re = Pattern.compile(""); + Pattern descriptions_line_re = Pattern.compile("\\bdescriptionsSuggester\\s*=\\s*new\\b"); + Pattern description_items_re = Pattern.compile("\"value\":\"([^\"]+)\""); + // %3A is '=' private boolean success; - public RetrieveTransactionsTask(WeakReference contextRef) { + public RetrieveTransactionsTask(WeakReference contextRef) { this.contextRef = contextRef; } private static final void L(String msg) { -// Log.d("transaction-parser", msg); + Log.d("transaction-parser", msg); } @Override protected void onProgressUpdate(Progress... values) { super.onProgressUpdate(values); - TransactionListFragment context = getContext(); + MainActivity context = getContext(); if (context == null) return; context.onRetrieveProgress(values[0]); } @Override protected void onPreExecute() { super.onPreExecute(); - TransactionListFragment context = getContext(); + MainActivity context = getContext(); if (context == null) return; context.onRetrieveStart(); } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); - TransactionListFragment context = getContext(); + MainActivity context = getContext(); if (context == null) return; context.onRetrieveDone(success); } @Override protected void onCancelled() { super.onCancelled(); - TransactionListFragment context = getContext(); + MainActivity context = getContext(); if (context == null) return; context.onRetrieveDone(false); } @SuppressLint("DefaultLocale") @Override - protected Void doInBackground(Params... params) { + protected Void doInBackground(Void... params) { + MobileLedgerProfile profile = Data.profile.get(); Progress progress = new Progress(); int maxTransactionId = Progress.INDETERMINATE; success = false; + ArrayList accountList = new ArrayList<>(); + ArrayList transactionList = new ArrayList<>(); + LedgerAccount lastAccount = null; + Data.backgroundTaskCount.incrementAndGet(); try { - HttpURLConnection http = - NetworkUtil.prepare_connection(params[0].getBackendPref(), "journal"); + HttpURLConnection http = NetworkUtil.prepare_connection("journal"); http.setAllowUserInteraction(false); publishProgress(progress); - TransactionListFragment ctx = getContext(); + MainActivity ctx = getContext(); if (ctx == null) return null; try (SQLiteDatabase db = MLDB.getWritableDatabase()) { try (InputStream resp = http.getInputStream()) { @@ -108,9 +126,13 @@ public class RetrieveTransactionsTask extends String.format("HTTP error %d", http.getResponseCode())); db.beginTransaction(); try { + String ledgerTitle = null; + db.execSQL("UPDATE transactions set keep=0"); + db.execSQL("update account_values set keep=0;"); + db.execSQL("update accounts set keep=0;"); - ParserState state = ParserState.EXPECTING_JOURNAL; + ParserState state = ParserState.EXPECTING_ACCOUNT; String line; BufferedReader buf = new BufferedReader(new InputStreamReader(resp, "UTF-8")); @@ -121,17 +143,63 @@ public class RetrieveTransactionsTask extends LedgerTransaction transaction = null; LINES: while ((line = buf.readLine()) != null) { - if (isCancelled()) break; + throwIfCancelled(); Matcher m; - //L(String.format("State is %d", state)); + m = commentPattern.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_JOURNAL: - if (!line.isEmpty() && (line.charAt(0) == ' ')) continue; + case EXPECTING_ACCOUNT: if (line.equals("

General Journal

")) { state = ParserState.EXPECTING_TRANSACTION; L("→ expecting transaction"); + Data.accounts.set(accountList); + continue; + } + m = account_name_re.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)); + + profile.storeAccount(acct_name); + lastAccount = new LedgerAccount(acct_name); + accountList.add(lastAccount); + + state = ParserState.EXPECTING_ACCOUNT_AMOUNT; + L("→ expecting account amount"); + } + break; + + case EXPECTING_ACCOUNT_AMOUNT: + m = account_value_re.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 = ""; + value = value.replace(',', '.'); + L("curr=" + currency + ", value=" + value); + profile.storeAccountValue(lastAccount.getName(), currency, + Float.valueOf(value)); + lastAccount.addAmount(Float.parseFloat(value), currency); + } + + if (match_found) { + state = ParserState.EXPECTING_ACCOUNT; + L("→ expecting account"); } + break; + case EXPECTING_TRANSACTION: if (!line.isEmpty() && (line.charAt(0) == ' ')) continue; m = transactionStartPattern.matcher(line); @@ -139,7 +207,7 @@ public class RetrieveTransactionsTask extends transactionId = Integer.valueOf(m.group(1)); state = ParserState.EXPECTING_TRANSACTION_DESCRIPTION; L(String.format( - "found transaction %d → expecting " + "description", + "found transaction %d → expecting description", transactionId)); progress.setProgress(++processedTransactionCount); if (maxTransactionId < transactionId) @@ -151,11 +219,12 @@ public class RetrieveTransactionsTask extends } m = endPattern.matcher(line); if (m.find()) { - L("--- transaction list complete ---"); + L("--- transaction value complete ---"); success = true; break LINES; } break; + case EXPECTING_TRANSACTION_DESCRIPTION: if (!line.isEmpty() && (line.charAt(0) == ' ')) continue; m = transactionDescriptionPattern.matcher(line); @@ -174,18 +243,26 @@ public class RetrieveTransactionsTask extends m.group(1), m.group(2))); } break; + case EXPECTING_TRANSACTION_DETAILS: if (line.isEmpty()) { // transaction data collected if (transaction.existsInDb(db)) { - db.execSQL("UPDATE transactions SET keep = 1 WHERE id" + - "=?", new Integer[]{transaction.getId()}); + db.execSQL("UPDATE transactions SET keep = 1 WHERE " + + "profile = ? and id=?", + new Object[]{profile.getUuid(), + transaction.getId() + }); matchedTransactionsCount++; - if (matchedTransactionsCount == 100) { + if (matchedTransactionsCount == + MATCHING_TRANSACTIONS_LIMIT) + { db.execSQL("UPDATE transactions SET keep=1 WHERE " + - "id < ?", - new Integer[]{transaction.getId()}); + "profile = ? and id < ?", + new Object[]{profile.getUuid(), + transaction.getId() + }); success = true; progress.setTotal(progress.getProgress()); publishProgress(progress); @@ -193,20 +270,17 @@ public class RetrieveTransactionsTask extends } } else { - db.execSQL("DELETE from transactions WHERE id=?", - new Integer[]{transaction.getId()}); - db.execSQL("DELETE from transaction_accounts WHERE " + - "transaction_id=?", - new Integer[]{transaction.getId()}); - transaction.insertInto(db); + profile.storeTransaction(transaction); matchedTransactionsCount = 0; progress.setTotal(maxTransactionId); } state = ParserState.EXPECTING_TRANSACTION; L(String.format( - "transaction %s saved → expecting " + "transaction", + "transaction %s saved → expecting transaction", transaction.getId())); + transaction.finishLoading(); + transactionList.add(transaction); // 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 @@ -222,38 +296,43 @@ public class RetrieveTransactionsTask extends 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("%s = %s", acc_name, amount)); + L(String.format("%d: %s = %s", transaction.getId(), + acc_name, amount)); } - else throw new IllegalStateException( - String.format("Can't parse transaction %d details", - transactionId)); + else throw new IllegalStateException(String.format( + "Can't parse transaction %d " + "details: %s", + transactionId, line)); } break; default: throw new RuntimeException( - String.format("Unknown parser state %s", state.name())); + String.format("Unknown parser updating %s", + state.name())); } } - if (!isCancelled()) { - db.execSQL("DELETE FROM transactions WHERE keep = 0"); - db.setTransactionSuccessful(); - } + + throwIfCancelled(); + + db.execSQL("DELETE FROM transactions WHERE profile=? AND keep = 0", + new String[]{profile.getUuid()}); + db.setTransactionSuccessful(); + + Log.d("db", "Updating transaction value stamp"); + Date now = new Date(); + profile.set_option_value(MLDB.OPT_LAST_SCRAPE, now.getTime()); + Data.lastUpdateDate.set(now); + Data.transactions.set(transactionList); } finally { db.endTransaction(); } } } - - if (success && !isCancelled()) { - Log.d("db", "Updating transaction list stamp"); - MLDB.set_option_value(MLDB.OPT_TRANSACTION_LIST_STAMP, new Date().getTime()); - ctx.model.reloadTransactions(ctx); - } } catch (MalformedURLException e) { error = R.string.err_bad_backend_url; @@ -267,26 +346,25 @@ public class RetrieveTransactionsTask extends error = R.string.err_net_io_error; e.printStackTrace(); } + catch (OperationCanceledException e) { + error = R.string.err_cancelled; + e.printStackTrace(); + } + finally { + Data.backgroundTaskCount.decrementAndGet(); + } return null; } - TransactionListFragment getContext() { + private MainActivity getContext() { return contextRef.get(); } - - private enum ParserState { - EXPECTING_JOURNAL, EXPECTING_TRANSACTION, EXPECTING_TRANSACTION_DESCRIPTION, - EXPECTING_TRANSACTION_DETAILS + private void throwIfCancelled() { + if (isCancelled()) throw new OperationCanceledException(null); } - public static class Params { - private SharedPreferences backendPref; - - public Params(SharedPreferences backendPref) { - this.backendPref = backendPref; - } - SharedPreferences getBackendPref() { - return backendPref; - } + private enum ParserState { + EXPECTING_ACCOUNT, EXPECTING_ACCOUNT_AMOUNT, EXPECTING_JOURNAL, EXPECTING_TRANSACTION, + EXPECTING_TRANSACTION_DESCRIPTION, EXPECTING_TRANSACTION_DETAILS } public class Progress {