/*
- * Copyright © 2019 Damyan Ivanov.
+ * Copyright © 2020 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
import android.os.AsyncTask;
import android.util.Log;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.ObjectWriter;
-
-import net.ktnx.mobileledger.json.ParsedLedgerTransaction;
+import net.ktnx.mobileledger.db.Profile;
+import net.ktnx.mobileledger.json.API;
+import net.ktnx.mobileledger.json.ApiNotSupportedException;
+import net.ktnx.mobileledger.json.Gateway;
import net.ktnx.mobileledger.model.LedgerTransaction;
import net.ktnx.mobileledger.model.LedgerTransactionAccount;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.utils.Globals;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.NetworkUtil;
+import net.ktnx.mobileledger.utils.SimpleDate;
import net.ktnx.mobileledger.utils.UrlEncodedFormData;
import java.io.BufferedReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static android.os.SystemClock.sleep;
import static net.ktnx.mobileledger.utils.Logger.debug;
+/* TODO: get rid of the custom session/cookie and auth code?
+ * (the last problem with the POST was the missing content-length header)
+ * This will resolve itself when hledger-web 1.14+ is released with Debian/stable,
+ * at which point the HTML form emulation can be dropped entirely
+ */
+
public class SendTransactionTask extends AsyncTask<LedgerTransaction, Void, Void> {
private final TaskCallback taskCallback;
+ private final Profile mProfile;
+ private final boolean simulate;
protected String error;
private String token;
private String session;
- private LedgerTransaction ltr;
- private MobileLedgerProfile mProfile;
- private boolean simulate = false;
+ private LedgerTransaction transaction;
- public SendTransactionTask(TaskCallback callback, MobileLedgerProfile profile,
+ public SendTransactionTask(TaskCallback callback, Profile profile,
boolean simulate) {
taskCallback = callback;
mProfile = profile;
this.simulate = simulate;
}
- public SendTransactionTask(TaskCallback callback, MobileLedgerProfile profile) {
+ public SendTransactionTask(TaskCallback callback, Profile profile) {
taskCallback = callback;
mProfile = profile;
simulate = false;
}
- private boolean sendOK() throws IOException {
+ private void sendOK(API apiVersion) throws IOException, ApiNotSupportedException {
+ HttpURLConnection http = NetworkUtil.prepareConnection(mProfile, "add");
+ http.setRequestMethod("PUT");
+ http.setRequestProperty("Content-Type", "application/json");
+ http.setRequestProperty("Accept", "*/*");
+
+ Gateway gateway = Gateway.forApiVersion(apiVersion);
+ String body = gateway.transactionSaveRequest(transaction);
+
+ Logger.debug("network", "Sending using API " + apiVersion);
+ sendRequest(http, body);
+ }
+ private void sendRequest(HttpURLConnection http, String body)
+ throws IOException, ApiNotSupportedException {
if (simulate) {
+ debug("network", "The request would be: " + body);
try {
Thread.sleep(1500);
if (Math.random() > 0.3)
Logger.debug("network", ex.toString());
}
- return true;
+ return;
}
- HttpURLConnection http = NetworkUtil.prepareConnection(mProfile, "add");
- http.setRequestMethod("PUT");
- http.setRequestProperty("Content-Type", "application/json");
- http.setRequestProperty("Accept", "*/*");
-
- ParsedLedgerTransaction jsonTransaction;
- jsonTransaction = ltr.toParsedLedgerTransaction();
- ObjectMapper mapper = new ObjectMapper();
- ObjectWriter writer = mapper.writerFor(ParsedLedgerTransaction.class);
- String body = writer.writeValueAsString(jsonTransaction);
-
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
http.setDoOutput(true);
http.setDoInput(true);
req.write(bodyBytes);
final int responseCode = http.getResponseCode();
- debug("network",
- String.format("Response: %d %s", responseCode, http.getResponseMessage()));
+ debug("network", String.format(Locale.US, "Response: %d %s", responseCode,
+ http.getResponseMessage()));
try (InputStream resp = http.getErrorStream()) {
case 200:
case 201:
break;
- case 405:
- return false; // will cause a retry with the legacy method
+ case 400:
+ case 405: {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(resp));
+ StringBuilder errorLines = new StringBuilder();
+ int count = 0;
+ while (count <= 5) {
+ String line = reader.readLine();
+ if (line == null)
+ break;
+ Logger.debug("network", line);
+
+ if (errorLines.length() != 0)
+ errorLines.append("\n");
+
+ errorLines.append(line);
+ count++;
+ }
+ throw new ApiNotSupportedException(errorLines.toString());
+ }
default:
BufferedReader reader = new BufferedReader(new InputStreamReader(resp));
String line = reader.readLine();
}
}
}
-
- return true;
}
private boolean legacySendOK() throws IOException {
HttpURLConnection http = NetworkUtil.prepareConnection(mProfile, "add");
if (token != null)
params.addPair("_token", token);
- Date transactionDate = ltr.getDate();
- if (transactionDate == null) {
- transactionDate = new GregorianCalendar().getTime();
- }
+ SimpleDate transactionDate = transaction.getDateIfAny();
+ if (transactionDate == null)
+ transactionDate = SimpleDate.today();
params.addPair("date", Globals.formatLedgerDate(transactionDate));
- params.addPair("description", ltr.getDescription());
- for (LedgerTransactionAccount acc : ltr.getAccounts()) {
+ params.addPair("description", transaction.getDescription());
+ for (LedgerTransactionAccount acc : transaction.getAccounts()) {
params.addPair("account", acc.getAccountName());
if (acc.isAmountSet())
params.addPair("amount", String.format(Locale.US, "%1.2f", acc.getAmount()));
}
}
}
-
@Override
protected Void doInBackground(LedgerTransaction... ledgerTransactions) {
error = null;
try {
- ltr = ledgerTransactions[0];
-
- if (!sendOK()) {
- int tried = 0;
- while (!legacySendOK()) {
- tried++;
- if (tried >= 2)
- throw new IOException(String.format("aborting after %d tries", tried));
- sleep(100);
- }
+ transaction = ledgerTransactions[0];
+
+ final API profileApiVersion = API.valueOf(mProfile.getApiVersion());
+ switch (profileApiVersion) {
+ case auto:
+ boolean sendOK = false;
+ for (API ver : API.allVersions) {
+ Logger.debug("network", "Trying version " + ver);
+ try {
+ sendOK(ver);
+ sendOK = true;
+ Logger.debug("network", "Version " + ver + " request succeeded");
+
+ break;
+ }
+ catch (ApiNotSupportedException e) {
+ Logger.debug("network", "Version " + ver + " seems not supported");
+ }
+ }
+
+ if (!sendOK) {
+ Logger.debug("network", "Trying HTML form emulation");
+ legacySendOkWithRetry();
+ }
+ break;
+ case html:
+ legacySendOkWithRetry();
+ break;
+ case v1_14:
+ case v1_15:
+ case v1_19_1:
+ sendOK(profileApiVersion);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected API version: " + profileApiVersion);
}
}
- catch (Exception e) {
+ catch (ApiNotSupportedException | Exception e) {
e.printStackTrace();
error = e.getMessage();
}
return null;
}
-
+ private void legacySendOkWithRetry() throws IOException {
+ int tried = 0;
+ while (!legacySendOK()) {
+ tried++;
+ if (tried >= 2)
+ throw new IOException(String.format("aborting after %d tries", tried));
+ sleep(100);
+ }
+ }
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
taskCallback.done(error);
}
-}
+
+}
\ No newline at end of file