]> git.ktnx.net Git - mobile-ledger.git/blobdiff - app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
finish support for multiple server APIs when retrieving data
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / async / RetrieveTransactionsTask.java
index 71131a075b9f3c2b183fcca8989548e731393fdd..d092a1cbfc55f1f0294cef1f6fc583248e14efa6 100644 (file)
@@ -24,19 +24,20 @@ import android.os.OperationCanceledException;
 
 import androidx.annotation.NonNull;
 
+import com.fasterxml.jackson.databind.RuntimeJsonMappingException;
+
 import net.ktnx.mobileledger.App;
 import net.ktnx.mobileledger.err.HTTPException;
-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.json.AccountListParser;
+import net.ktnx.mobileledger.json.ApiNotSupportedException;
+import net.ktnx.mobileledger.json.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.MainModel;
+import net.ktnx.mobileledger.utils.Logger;
 import net.ktnx.mobileledger.utils.NetworkUtil;
 
 import java.io.BufferedReader;
@@ -74,12 +75,13 @@ public class RetrieveTransactionsTask extends
     private static final Pattern reDecimalPoint = Pattern.compile("\\.\\d\\d?$");
     private static final Pattern reDecimalComma = Pattern.compile(",\\d\\d?$");
     // %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(
             "<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
-    private MainModel mainModel;
-    private MobileLedgerProfile profile;
-    private List<LedgerAccount> prevAccounts;
+    private final MainModel mainModel;
+    private final MobileLedgerProfile profile;
+    private final List<LedgerAccount> prevAccounts;
     private int expectedPostingsCount = -1;
     public RetrieveTransactionsTask(@NonNull MainModel mainModel,
                                     @NonNull MobileLedgerProfile profile,
@@ -118,6 +120,9 @@ public class RetrieveTransactionsTask extends
             return null;
         }
     }
+    public MobileLedgerProfile getProfile() {
+        return profile;
+    }
     @Override
     protected void onProgressUpdate(Progress... values) {
         super.onProgressUpdate(values);
@@ -355,9 +360,9 @@ public class RetrieveTransactionsTask extends
             throwIfCancelled();
         }
     }
-    private @NonNull
-    LedgerAccount ensureAccountExists(String accountName, HashMap<String, LedgerAccount> map,
-                                      ArrayList<LedgerAccount> createdAccounts) {
+    @NonNull
+    public LedgerAccount ensureAccountExists(String accountName, HashMap<String, LedgerAccount> map,
+                                             ArrayList<LedgerAccount> createdAccounts) {
         LedgerAccount acc = map.get(accountName);
 
         if (acc != null)
@@ -376,7 +381,43 @@ public class RetrieveTransactionsTask extends
         createdAccounts.add(acc);
         return acc;
     }
-    private List<LedgerAccount> retrieveAccountList() throws IOException, HTTPException {
+    public void addNumberOfPostings(int number) {
+        expectedPostingsCount += number;
+    }
+    private List<LedgerAccount> retrieveAccountList()
+            throws IOException, HTTPException, ApiNotSupportedException {
+        final SendTransactionTask.API apiVersion = profile.getApiVersion();
+        if (apiVersion.equals(SendTransactionTask.API.auto)) {
+            return retrieveAccountListAnyVersion();
+        }
+        else if (apiVersion.equals(SendTransactionTask.API.html)) {
+            Logger.debug("json",
+                    "Declining using JSON API for /accounts with configured legacy API version");
+            return null;
+        }
+        else {
+            return retrieveAccountListForVersion(apiVersion);
+        }
+    }
+    private List<LedgerAccount> retrieveAccountListAnyVersion()
+            throws HTTPException, ApiNotSupportedException {
+        for (SendTransactionTask.API ver : SendTransactionTask.API.allVersions) {
+            try {
+                return retrieveAccountListForVersion(ver);
+            }
+            catch (Exception e) {
+                Logger.debug("json",
+                        String.format(Locale.US, "Error during account list retrieval using API %s",
+                                ver.getDescription()));
+            }
+
+            throw new ApiNotSupportedException();
+        }
+
+        throw new RuntimeException("This should never be reached");
+    }
+    private List<LedgerAccount> retrieveAccountListForVersion(SendTransactionTask.API version)
+            throws IOException, HTTPException {
         HttpURLConnection http = NetworkUtil.prepareConnection(profile, "accounts");
         http.setAllowUserInteraction(false);
         switch (http.getResponseCode()) {
@@ -400,58 +441,15 @@ public class RetrieveTransactionsTask extends
             if (http.getResponseCode() != 200)
                 throw new IOException(String.format("HTTP error %d", http.getResponseCode()));
 
-            AccountListParser parser = new AccountListParser(resp);
+            AccountListParser parser = AccountListParser.forApiVersion(version, resp);
             expectedPostingsCount = 0;
 
             while (true) {
                 throwIfCancelled();
-                ParsedLedgerAccount parsedAccount = parser.nextAccount();
-                if (parsedAccount == null) {
+                LedgerAccount acc = parser.nextAccount(this, map);
+                if (acc == null)
                     break;
-                }
-                expectedPostingsCount += parsedAccount.getAnumpostings();
-                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<LedgerAccount> 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);
-
-                String lastCurrency = null;
-                float lastCurrencyAmount = 0;
-                for (ParsedBalance b : parsedAccount.getAibalance()) {
-                    throwIfCancelled();
-                    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;
-                    }
-                }
-                if (lastCurrency != null) {
-                    acc.addAmount(lastCurrencyAmount, lastCurrency);
-                }
-                for (LedgerAccount p : createdParents)
-                    acc.propagateAmountsTo(p);
             }
             throwIfCancelled();
         }
@@ -468,7 +466,40 @@ public class RetrieveTransactionsTask extends
         return list;
     }
     private List<LedgerTransaction> retrieveTransactionList()
-            throws IOException, ParseException, HTTPException {
+            throws ParseException, HTTPException, IOException, ApiNotSupportedException {
+        final SendTransactionTask.API apiVersion = profile.getApiVersion();
+        if (apiVersion.equals(SendTransactionTask.API.auto)) {
+            return retrieveTransactionListAnyVersion();
+        }
+        else if (apiVersion.equals(SendTransactionTask.API.html)) {
+            Logger.debug("json",
+                    "Declining using JSON API for /accounts with configured legacy API version");
+            return null;
+        }
+        else {
+            return retrieveTransactionListForVersion(apiVersion);
+        }
+
+    }
+    private List<LedgerTransaction> retrieveTransactionListAnyVersion()
+            throws ApiNotSupportedException {
+        for (SendTransactionTask.API ver : SendTransactionTask.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()));
+            }
+
+            throw new ApiNotSupportedException();
+        }
+
+        throw new RuntimeException("This should never be reached");
+    }
+    private List<LedgerTransaction> retrieveTransactionListForVersion(
+            SendTransactionTask.API apiVersion) throws IOException, ParseException, HTTPException {
         Progress progress = new Progress();
         progress.setTotal(expectedPostingsCount);
 
@@ -487,18 +518,17 @@ public class RetrieveTransactionsTask extends
         try (InputStream resp = http.getInputStream()) {
             throwIfCancelled();
 
-            TransactionListParser parser = new TransactionListParser(resp);
+            TransactionListParser parser = TransactionListParser.forApiVersion(apiVersion, resp);
 
             int processedPostings = 0;
 
             while (true) {
                 throwIfCancelled();
-                ParsedLedgerTransaction parsedTransaction = parser.nextTransaction();
+                LedgerTransaction transaction = parser.nextTransaction();
                 throwIfCancelled();
-                if (parsedTransaction == null)
+                if (transaction == null)
                     break;
 
-                LedgerTransaction transaction = parsedTransaction.asLedgerTransaction();
                 trList.add(transaction);
 
                 progress.setProgress(processedPostings += transaction.getAccounts()
@@ -537,10 +567,15 @@ public class RetrieveTransactionsTask extends
         List<LedgerTransaction> transactions;
         try {
             accounts = retrieveAccountList();
+            // accounts is null in API-version auto-detection and means
+            // requesting 'html' API version via the JSON classes
+            // this can't work, and the null results in the legacy code below
+            // being called
             if (accounts == null)
                 transactions = null;
             else
                 transactions = retrieveTransactionList();
+
             if (accounts == null || transactions == null) {
                 accounts = new ArrayList<>();
                 transactions = new ArrayList<>();
@@ -563,6 +598,10 @@ public class RetrieveTransactionsTask extends
             e.printStackTrace();
             return new Result(e.getLocalizedMessage());
         }
+        catch (RuntimeJsonMappingException e) {
+            e.printStackTrace();
+            return new Result(Result.ERR_JSON_PARSER_ERROR);
+        }
         catch (ParseException e) {
             e.printStackTrace();
             return new Result("Network error");
@@ -571,11 +610,15 @@ public class RetrieveTransactionsTask extends
             e.printStackTrace();
             return new Result("Operation cancelled");
         }
+        catch (ApiNotSupportedException e) {
+            e.printStackTrace();
+            return new Result("Server version not supported");
+        }
         finally {
             Data.backgroundTaskFinished();
         }
     }
-    private void throwIfCancelled() {
+    public void throwIfCancelled() {
         if (isCancelled())
             throw new OperationCanceledException(null);
     }
@@ -660,6 +703,7 @@ public class RetrieveTransactionsTask extends
     }
 
     public static class Result {
+        public static String ERR_JSON_PARSER_ERROR = "err_json_parser";
         public String error;
         public List<LedgerAccount> accounts;
         public List<LedgerTransaction> transactions;