import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.OperationCanceledException;
-import android.util.Log;
import net.ktnx.mobileledger.err.HTTPException;
import net.ktnx.mobileledger.json.AccountListParser;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
public class RetrieveTransactionsTask
extends AsyncTask<Void, RetrieveTransactionsTask.Progress, String> {
- 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("<tr class=\"title\" " +
"id=\"transaction-(\\d+)\"><td class=\"date\"[^\"]*>([\\d.-]+)</td>");
Pattern.compile("^\\s+(\\S[\\S\\s]+\\S)\\s\\s+([-+]?\\d[\\d,.]*)(?:\\s+(\\S+)$)?");
private static final Pattern reEnd = Pattern.compile("\\bid=\"addmodal\"");
private WeakReference<MainActivity> contextRef;
- private int error;
// %3A is '='
private Pattern reAccountName = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\"");
private Pattern reAccountValue = Pattern.compile(
"<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
- public RetrieveTransactionsTask(WeakReference<MainActivity> contextRef) {
+ private MobileLedgerProfile profile;
+ public RetrieveTransactionsTask(WeakReference<MainActivity> contextRef,
+ MobileLedgerProfile profile) {
this.contextRef = contextRef;
+ this.profile = profile;
}
private static void L(String msg) {
- //Log.d("transaction-parser", msg);
+ //debug("transaction-parser", msg);
}
@Override
protected void onProgressUpdate(Progress... values) {
if (context == null) return;
context.onRetrieveDone(null);
}
- private String retrieveTransactionListLegacy(MobileLedgerProfile profile)
+ private String retrieveTransactionListLegacy()
throws IOException, ParseException, HTTPException {
Progress progress = new Progress();
int maxTransactionId = Progress.INDETERMINATE;
ArrayList<LedgerAccount> accountList = new ArrayList<>();
HashMap<String, Void> accountNames = new HashMap<>();
+ HashMap<String, LedgerAccount> syntheticAccounts = new HashMap<>();
LedgerAccount lastAccount = null, prevAccount = null;
boolean onlyStarred = Data.optShowOnlyStarred.get();
m = reComment.matcher(line);
if (m.find()) {
// TODO: comments are ignored for now
- Log.v("transaction-parser", "Ignoring comment");
+// Log.v("transaction-parser", "Ignoring comment");
continue;
}
//L(String.format("State is %d", updating));
.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
+ // only one child so we create a synthetic parent account record,
+ // copying the amounts when child's amounts are parsed
String parentName = lastAccount.getParentName();
if (parentName != null) {
Stack<String> toAppend = new Stack<>();
parentName =
new LedgerAccount(parentName).getParentName();
}
+ syntheticAccounts.clear();
while (!toAppend.isEmpty()) {
String aName = toAppend.pop();
- LedgerAccount acc = new LedgerAccount(aName);
- acc.setHiddenByStar(lastAccount.isHiddenByStar());
+ LedgerAccount acc = profile.tryLoadAccount(db, aName);
+ if (acc == null) {
+ acc = new LedgerAccount(aName);
+ acc.setHiddenByStar(lastAccount.isHiddenByStar());
+ 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);
L(String.format("gap-filling with %s", aName));
accountNames.put(aName, null);
profile.storeAccount(db, acc);
+ syntheticAccounts.put(aName, acc);
}
}
if (currency == null) currency = "";
value = value.replace(',', '.');
L("curr=" + currency + ", value=" + value);
+ final float val = Float.parseFloat(value);
profile.storeAccountValue(db, lastAccount.getName(), currency,
- Float.valueOf(value));
- lastAccount.addAmount(Float.parseFloat(value), currency);
+ val);
+ lastAccount.addAmount(val, currency);
+ for (LedgerAccount syn : syntheticAccounts.values()) {
+ syn.addAmount(val, currency);
+ profile.storeAccountValue(db, syn.getName(), currency,
+ val);
+ }
}
if (match_found) {
if (m.find()) {
transactionId = Integer.valueOf(m.group(1));
state = ParserState.EXPECTING_TRANSACTION_DESCRIPTION;
- L(String.format("found transaction %d → expecting description",
+ L(String.format(Locale.ENGLISH,
+ "found transaction %d → expecting description",
transactionId));
progress.setProgress(++processedTransactionCount);
if (maxTransactionId < transactionId)
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)));
+ L(String.format(Locale.ENGLISH,
+ "transaction %d created for %s (%s) →" +
+ " expecting details", transactionId, date, m.group(2)));
}
break;
transaction.addAccount(
new LedgerTransactionAccount(acc_name,
Float.valueOf(amount), currency));
- L(String.format("%d: %s = %s", transaction.getId(),
- acc_name, amount));
+ L(String.format(Locale.ENGLISH, "%d: %s = %s",
+ transaction.getId(), acc_name, amount));
}
else throw new IllegalStateException(String.format(
"Can't parse transaction %d " + "details: %s",
new String[]{profile.getUuid()});
db.execSQL("update accounts set keep=0 where profile=?;", new String[]{profile.getUuid()});
}
- private boolean retrieveAccountList(MobileLedgerProfile profile)
+ private boolean retrieveAccountList()
throws IOException, HTTPException {
Progress progress = new Progress();
return true;
}
- private boolean retrieveTransactionList(MobileLedgerProfile profile)
+ private boolean retrieveTransactionList()
throws IOException, ParseException, HTTPException {
Progress progress = new Progress();
int maxTransactionId = Progress.INDETERMINATE;
if (transactionOrder == DetectedTransactionOrder.UNKNOWN) {
if (orderAccumulator > 30) {
transactionOrder = DetectedTransactionOrder.FILE;
- Log.d("rtt", String.format(
+ debug("rtt", String.format(Locale.ENGLISH,
"Detected native file order after %d transactions (factor %d)",
processedTransactionCount, orderAccumulator));
progress.setTotal(Data.transactions.size());
}
else if (orderAccumulator < -30) {
transactionOrder = DetectedTransactionOrder.REVERSE_CHRONOLOGICAL;
- Log.d("rtt", String.format(
+ debug("rtt", String.format(Locale.ENGLISH,
"Detected reverse chronological order after %d transactions (factor %d)",
processedTransactionCount, orderAccumulator));
}
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) {
return "Operation cancelled";
}
finally {
- Data.backgroundTaskCount.decrementAndGet();
+ Data.backgroundTaskFinished();
}
}
private MainActivity getContext() {