aboutsummaryrefslogtreecommitdiffstats
path: root/main
diff options
context:
space:
mode:
authorrsudev <rasch@munin-soft.de>2013-10-04 08:31:29 +0200
committerrsudev <rasch@munin-soft.de>2013-10-14 09:38:02 +0200
commitdfc984f715b443efca23615ae0dd763037bddc0f (patch)
treebd94a4a3ddd38beaee1d2818d2543af8a4ca41fb /main
parent27a6bfe43a395c5e86981c8c76763a6e52e8d3aa (diff)
downloadcgeo-dfc984f715b443efca23615ae0dd763037bddc0f.zip
cgeo-dfc984f715b443efca23615ae0dd763037bddc0f.tar.gz
cgeo-dfc984f715b443efca23615ae0dd763037bddc0f.tar.bz2
Implements part of #3267, Detect and report time-skew that prevents okapi access
Reports back issues on the main screen, but not yet during the authorization process
Diffstat (limited to 'main')
-rw-r--r--main/res/values/strings.xml2
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OkapiClient.java61
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OkapiError.java87
-rw-r--r--main/src/cgeo/geocaching/connector/oc/UserInfo.java18
-rw-r--r--main/src/cgeo/geocaching/network/Network.java6
5 files changed, 157 insertions, 17 deletions
diff --git a/main/res/values/strings.xml b/main/res/values/strings.xml
index 82aff94..f65a009 100644
--- a/main/res/values/strings.xml
+++ b/main/res/values/strings.xml
@@ -409,6 +409,8 @@
<string name="init_login_popup_failed">Login failed</string>
<string name="init_login_popup_failed_reason">Login failed:</string>
<string name="init_login_popup_not_authorized">Not authorized</string>
+ <string name="init_login_popup_invalid_timestamp">Local time invalid, adjust device time</string>
+ <string name="init_login_popup_invalid_token">Authorization invalid, re-authorize</string>
<string name="init_signature">Signature</string>
<string name="init_template_help">Placeholder strings like [NAME] will be expanded later when this template is used.</string>
<string name="init_signature_template_button">Insert Template</string>
diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java
index e3cf1ca..686e314 100644
--- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java
+++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java
@@ -31,9 +31,13 @@ import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.SynchronizedDateFormat;
+import ch.boye.httpclientandroidlib.HttpResponse;
+
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -129,13 +133,9 @@ final class OkapiClient {
params.add("fields", getFullFields(ocapiConn));
params.add("attribution_append", "none");
- final JSONObject data = request(ocapiConn, OkapiService.SERVICE_CACHE, params);
-
- if (data == null) {
- return null;
- }
+ final JSONResult result = request(ocapiConn, OkapiService.SERVICE_CACHE, params);
- return parseCache(data);
+ return result.isSuccess ? parseCache(result.data) : null;
}
public static List<Geocache> getCachesAround(final Geopoint center, final OCApiConnector connector) {
@@ -180,7 +180,7 @@ final class OkapiClient {
params.add("search_params", new JSONObject(valueMap).toString());
addRetrieveParams(params, connector);
- final JSONObject data = request(connector, OkapiService.SERVICE_SEARCH_AND_RETRIEVE, params);
+ final JSONObject data = request(connector, OkapiService.SERVICE_SEARCH_AND_RETRIEVE, params).data;
if (data == null) {
return Collections.emptyList();
@@ -213,7 +213,7 @@ final class OkapiClient {
final Parameters params = new Parameters("cache_code", cache.getGeocode());
params.add("watched", watched ? "true" : "false");
- final JSONObject data = request(connector, OkapiService.SERVICE_MARK_CACHE, params);
+ final JSONObject data = request(connector, OkapiService.SERVICE_MARK_CACHE, params).data;
if (data == null) {
return false;
@@ -237,7 +237,7 @@ final class OkapiClient {
params.add("password", logPassword);
}
- final JSONObject data = request(connector, OkapiService.SERVICE_SUBMIT_LOG, params);
+ final JSONObject data = request(connector, OkapiService.SERVICE_SUBMIT_LOG, params).data;
if (data == null) {
return new LogResult(StatusCode.LOG_POST_ERROR, "");
@@ -623,14 +623,15 @@ final class OkapiClient {
return res.toString();
}
- private static JSONObject request(final OCApiConnector connector, final OkapiService service, final Parameters params) {
+ @NonNull
+ private static JSONResult request(final OCApiConnector connector, final OkapiService service, final Parameters params) {
if (connector == null) {
- return null;
+ return new JSONResult(null);
}
final String host = connector.getHost();
if (StringUtils.isBlank(host)) {
- return null;
+ return new JSONResult(null);
}
params.add("langpref", getPreferredLanguage());
@@ -643,7 +644,7 @@ final class OkapiClient {
}
final String uri = "http://" + host + service.methodName;
- return Network.requestJSON(uri, params);
+ return new JSONResult(Network.getRequest(uri, params));
}
private static String getPreferredLanguage() {
@@ -695,12 +696,16 @@ final class OkapiClient {
public static UserInfo getUserInfo(final OCApiLiveConnector connector) {
final Parameters params = new Parameters("fields", USER_INFO_FIELDS);
- final JSONObject data = request(connector, OkapiService.SERVICE_USER, params);
+ final JSONResult result = request(connector, OkapiService.SERVICE_USER, params);
- if (data == null) {
- return new UserInfo(StringUtils.EMPTY, 0, UserInfoStatus.FAILED);
+ if (!result.isSuccess) {
+ final OkapiError error = new OkapiError(result.data);
+ Log.e("OkapiClient.getUserInfo: error getting user info: '" + error.getMessage() + "'");
+ return new UserInfo(StringUtils.EMPTY, 0, UserInfoStatus.getFromOkapiError(error.getResult()));
}
+ JSONObject data = result.data;
+
String name = StringUtils.EMPTY;
boolean successUserName = false;
@@ -728,4 +733,28 @@ final class OkapiClient {
return new UserInfo(name, finds, successUserName && successFinds ? UserInfoStatus.SUCCESSFUL : UserInfoStatus.FAILED);
}
+ /**
+ * Encapsulates response state and content of an HTTP-request that expects a JSON result. <code>isSuccess</code> is
+ * only true, if the response state was success and <code>data</code> is not null.
+ */
+ private static class JSONResult {
+
+ public final boolean isSuccess;
+ public final JSONObject data;
+
+ public JSONResult(final @Nullable HttpResponse response) {
+ boolean isSuccess = Network.isSuccess(response);
+ final String responseData = Network.getResponseDataAlways(response);
+ JSONObject data = null;
+ if (responseData != null) {
+ try {
+ data = new JSONObject(responseData);
+ } catch (final JSONException e) {
+ Log.w("JSONResult", e);
+ }
+ }
+ this.data = data;
+ this.isSuccess = isSuccess && data != null;
+ }
+ }
}
diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiError.java b/main/src/cgeo/geocaching/connector/oc/OkapiError.java
new file mode 100644
index 0000000..7faf2c7
--- /dev/null
+++ b/main/src/cgeo/geocaching/connector/oc/OkapiError.java
@@ -0,0 +1,87 @@
+package cgeo.geocaching.connector.oc;
+
+import cgeo.geocaching.utils.Log;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Handles the JSON error response from OKAPI
+ */
+public class OkapiError {
+
+ /**
+ * List of detected errors OKAPI might return
+ */
+ public enum OkapiErrors {
+ NO_ERROR,
+ UNSPECIFIED,
+ INVALID_TIMESTAMP,
+ INVALID_TOKEN;
+ }
+
+ @NonNull private final OkapiErrors state;
+ @NonNull private final String message;
+
+ public OkapiError(@Nullable JSONObject data) {
+
+ // A null-response is by definition an error (some exception occurred somewhere in the flow)
+ if (data == null) {
+ state = OkapiErrors.UNSPECIFIED;
+ message = StringUtils.EMPTY;
+ return;
+ }
+ // Second possibility: we get an error object as return (@see http://opencaching.pl/okapi/introduction.html#errors)
+ if (data.has("error")) {
+ String localmessage = null;
+ OkapiErrors localstate = OkapiErrors.UNSPECIFIED;
+ try {
+ JSONObject error = data.getJSONObject("error");
+ // Check reason_stack element to look for the specific oauth problems we want to report back
+ if (error.has("reason_stack")) {
+ String reason = error.getString("reason_stack");
+ if (StringUtils.contains(reason, "invalid_oauth_request")) {
+ if (StringUtils.contains(reason, "invalid_timestamp")) {
+ localstate = OkapiErrors.INVALID_TIMESTAMP;
+ } else if (StringUtils.contains(reason, "invalid_token")) {
+ localstate = OkapiErrors.INVALID_TOKEN;
+ }
+ }
+ }
+ // Check if we can extract a message as well
+ if (error.has("developer_message")) {
+ localmessage = error.getString("developer_message");
+ assert localmessage != null; // by virtue of defaultString
+ }
+ } catch (JSONException ex) {
+ Log.d("OkapiError: Failed to parse JSON", ex);
+ localstate = OkapiErrors.UNSPECIFIED;
+ }
+ state = localstate;
+ message = StringUtils.defaultString(localmessage);
+ return;
+ }
+
+ // Third possibility: some other response, everything is fine!
+ state = OkapiErrors.NO_ERROR;
+ message = StringUtils.EMPTY;
+ }
+
+ public boolean isError() {
+ return state != OkapiErrors.NO_ERROR;
+ }
+
+ @NonNull
+ public OkapiErrors getResult() {
+ return state;
+ }
+
+ @NonNull
+ public String getMessage() {
+ return message;
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/connector/oc/UserInfo.java b/main/src/cgeo/geocaching/connector/oc/UserInfo.java
index 0dc0440..c8b37cd 100644
--- a/main/src/cgeo/geocaching/connector/oc/UserInfo.java
+++ b/main/src/cgeo/geocaching/connector/oc/UserInfo.java
@@ -1,6 +1,7 @@
package cgeo.geocaching.connector.oc;
import cgeo.geocaching.R;
+import cgeo.geocaching.connector.oc.OkapiError.OkapiErrors;
public class UserInfo {
@@ -8,13 +9,28 @@ public class UserInfo {
NOT_RETRIEVED(R.string.init_login_popup_working),
SUCCESSFUL(R.string.init_login_popup_ok),
FAILED(R.string.init_login_popup_failed),
- NOT_SUPPORTED(R.string.init_login_popup_not_authorized);
+ NOT_SUPPORTED(R.string.init_login_popup_not_authorized),
+ INVALID_TIMESTAMP(R.string.init_login_popup_invalid_timestamp),
+ INVALID_TOKEN(R.string.init_login_popup_invalid_token);
public final int resId;
UserInfoStatus(int resId) {
this.resId = resId;
}
+
+ public static UserInfoStatus getFromOkapiError(OkapiErrors result) {
+ switch (result) {
+ case NO_ERROR:
+ return SUCCESSFUL;
+ case INVALID_TIMESTAMP:
+ return INVALID_TIMESTAMP;
+ case INVALID_TOKEN:
+ return INVALID_TOKEN;
+ default:
+ return FAILED;
+ }
+ }
}
private final String name;
diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java
index 1810216..e891d3b 100644
--- a/main/src/cgeo/geocaching/network/Network.java
+++ b/main/src/cgeo/geocaching/network/Network.java
@@ -34,6 +34,7 @@ import ch.boye.httpclientandroidlib.params.CoreProtocolPNames;
import ch.boye.httpclientandroidlib.params.HttpParams;
import ch.boye.httpclientandroidlib.protocol.HttpContext;
import ch.boye.httpclientandroidlib.util.EntityUtils;
+
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.Nullable;
@@ -449,6 +450,11 @@ public abstract class Network {
return Network.getResponseData(response, true);
}
+ @Nullable
+ public static String getResponseDataAlways(@Nullable final HttpResponse response) {
+ return response != null ? getResponseDataNoError(response, false) : null;
+ }
+
/**
* Get the body of a HTTP response.
*