diff options
Diffstat (limited to 'main/src/cgeo/geocaching/connector')
27 files changed, 452 insertions, 240 deletions
diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index 53a3bcb..6d8d79e 100644 --- a/main/src/cgeo/geocaching/connector/AbstractConnector.java +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -16,10 +16,10 @@ import cgeo.geocaching.connector.capability.ISearchByViewPort; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.utils.RunnableWithArgument; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import rx.functions.Action1; import java.util.ArrayList; import java.util.Collection; @@ -255,21 +255,21 @@ public abstract class AbstractConnector implements IConnector { List<UserAction> actions = getDefaultUserActions(); if (this instanceof ISearchByOwner) { - actions.add(new UserAction(R.string.user_menu_view_hidden, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_view_hidden, new Action1<Context>() { @Override - public void run(Context context) { + public void call(Context context) { CacheListActivity.startActivityOwner(context.activity, context.userName); } })); } if (this instanceof ISearchByFinder) { - actions.add(new UserAction(R.string.user_menu_view_found, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_view_found, new Action1<UserAction.Context>() { @Override - public void run(Context context) { - CacheListActivity.startActivityUserName(context.activity, context.userName); + public void call(Context context) { + CacheListActivity.startActivityFinder(context.activity, context.userName); } })); } @@ -283,10 +283,10 @@ public abstract class AbstractConnector implements IConnector { public List<UserAction> getDefaultUserActions() { final ArrayList<UserAction> actions = new ArrayList<UserAction>(); if (ContactsAddon.isAvailable()) { - actions.add(new UserAction(R.string.user_menu_open_contact, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_open_contact, new Action1<UserAction.Context>() { @Override - public void run(Context context) { + public void call(Context context) { ContactsAddon.openContactCard(context.activity, context.userName); } })); diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index 0081951..18344f5 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -28,6 +28,11 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import rx.Observable; +import rx.schedulers.Schedulers; +import rx.functions.Func1; +import rx.functions.Func2; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -176,14 +181,30 @@ public final class ConnectorFactory { } /** @see ISearchByViewPort#searchByViewport */ - public static SearchResult searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) { - final SearchResult result = new SearchResult(); - for (final ISearchByViewPort connector : searchByViewPortConns) { - if (connector.isActive()) { - result.addSearchResult(connector.searchByViewport(viewport, tokens)); + public static Observable<SearchResult> searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) { + return Observable.from(searchByViewPortConns).filter(new Func1<ISearchByViewPort, Boolean>() { + @Override + public Boolean call(final ISearchByViewPort connector) { + return connector.isActive(); } - } - return result; + }).parallel(new Func1<Observable<ISearchByViewPort>, Observable<SearchResult>>() { + @Override + public Observable<SearchResult> call(final Observable<ISearchByViewPort> connector) { + return connector.map(new Func1<ISearchByViewPort, SearchResult>() { + @Override + public SearchResult call(final ISearchByViewPort connector) { + return connector.searchByViewport(viewport, tokens); + } + }); + } + }, Schedulers.io()).reduce(new SearchResult(), new Func2<SearchResult, SearchResult, SearchResult>() { + + @Override + public SearchResult call(final SearchResult result, final SearchResult searchResult) { + result.addSearchResult(searchResult); + return result; + } + }); } public static String getGeocodeFromURL(final String url) { @@ -200,6 +221,12 @@ public final class ConnectorFactory { return TRACKABLE_CONNECTORS; } + /** + * Get the geocode of a trackable from a URL. + * + * @param url + * @return {@code null} if the URL cannot be decoded + */ public static String getTrackableFromURL(final String url) { if (url == null) { return null; diff --git a/main/src/cgeo/geocaching/connector/UserAction.java b/main/src/cgeo/geocaching/connector/UserAction.java index d0c97bb..e9ee4a3 100644 --- a/main/src/cgeo/geocaching/connector/UserAction.java +++ b/main/src/cgeo/geocaching/connector/UserAction.java @@ -1,8 +1,7 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.utils.RunnableWithArgument; - import org.eclipse.jdt.annotation.NonNull; +import rx.functions.Action1; import android.app.Activity; @@ -19,14 +18,15 @@ public class UserAction { } public final int displayResourceId; - private final @NonNull RunnableWithArgument<Context> runnable; + private final @NonNull + Action1<Context> runnable; - public UserAction(int displayResourceId, final @NonNull RunnableWithArgument<UserAction.Context> runnable) { + public UserAction(int displayResourceId, final @NonNull Action1<Context> runnable) { this.displayResourceId = displayResourceId; this.runnable = runnable; } public void run(Context context) { - runnable.run(context); + runnable.call(context); } } diff --git a/main/src/cgeo/geocaching/connector/capability/FieldNotesCapability.java b/main/src/cgeo/geocaching/connector/capability/FieldNotesCapability.java new file mode 100644 index 0000000..4da9705 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/capability/FieldNotesCapability.java @@ -0,0 +1,13 @@ +package cgeo.geocaching.connector.capability; + +import cgeo.geocaching.connector.IConnector; + +import java.io.File; + +/** + * Connector interface to implement an upload of (already exported) field notes + * + */ +public interface FieldNotesCapability extends IConnector { + public boolean uploadFieldNotes(final File exportFile); +} diff --git a/main/src/cgeo/geocaching/connector/ec/ECApi.java b/main/src/cgeo/geocaching/connector/ec/ECApi.java index 03fce4d..702e557 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECApi.java +++ b/main/src/cgeo/geocaching/connector/ec/ECApi.java @@ -15,12 +15,12 @@ import cgeo.geocaching.list.StoredList; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.SynchronizedDateFormat; import ch.boye.httpclientandroidlib.HttpResponse; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.time.FastDateFormat; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -39,7 +39,7 @@ public class ECApi { private static final String API_HOST = "https://extremcaching.com/exports/"; private static final ECLogin ecLogin = ECLogin.getInstance(); - private static final FastDateFormat LOG_DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US); + private static final SynchronizedDateFormat LOG_DATE_FORMAT = new SynchronizedDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US); public static String getIdFromGeocode(final String geocode) { return StringUtils.removeStartIgnoreCase(geocode, "EC"); diff --git a/main/src/cgeo/geocaching/connector/ec/ECConnector.java b/main/src/cgeo/geocaching/connector/ec/ECConnector.java index 6da076b..71716fe 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECConnector.java +++ b/main/src/cgeo/geocaching/connector/ec/ECConnector.java @@ -140,7 +140,7 @@ public class ECConnector extends AbstractConnector implements ISearchByGeocode, // invoke settings activity to insert login details if (status == StatusCode.NO_LOGIN_INFO_STORED && fromActivity != null) { - SettingsActivity.jumpToServicesPage(fromActivity); + SettingsActivity.openForScreen(R.string.preference_screen_ec, fromActivity); } } return status == StatusCode.NO_ERROR; diff --git a/main/src/cgeo/geocaching/connector/ec/ECLogin.java b/main/src/cgeo/geocaching/connector/ec/ECLogin.java index 52bd423..012bdc9 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECLogin.java +++ b/main/src/cgeo/geocaching/connector/ec/ECLogin.java @@ -10,7 +10,6 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.Nullable; diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index e946748..925f6f0 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -10,6 +10,7 @@ import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.connector.ILoggingManager; import cgeo.geocaching.connector.UserAction; +import cgeo.geocaching.connector.capability.FieldNotesCapability; import cgeo.geocaching.connector.capability.ICredentials; import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.connector.capability.ISearchByCenter; @@ -23,26 +24,29 @@ import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.loaders.RecaptchaReceiver; import cgeo.geocaching.network.Network; +import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.settings.SettingsActivity; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RunnableWithArgument; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import rx.functions.Action1; + import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Handler; +import java.io.File; import java.util.List; import java.util.regex.Pattern; -public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort, ISearchByKeyword, ILogin, ICredentials, ISearchByOwner, ISearchByFinder { +public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort, ISearchByKeyword, ILogin, ICredentials, ISearchByOwner, ISearchByFinder, FieldNotesCapability { private static final String CACHE_URL_SHORT = "http://coord.info/"; // Double slash is used to force open in browser @@ -91,7 +95,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override public boolean supportsPersonalNote() { - return Settings.isPremiumMember(); + return Settings.isGCPremiumMember(); } @Override @@ -285,7 +289,22 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override protected String getCacheUrlPrefix() { - return CACHE_URL_SHORT; + return null; // UNUSED + } + + @Override + public String getGeocodeFromUrl(String url) { + // coord.info URLs + String code = StringUtils.substringAfterLast(url, "coord.info/"); + if (code != null && canHandle(code)) { + return code; + } + // expanded geocaching.com URLs + code = StringUtils.substringBetween(url, "/geocache/", "_"); + if (code != null && canHandle(code)) { + return code; + } + return null; } @Override @@ -316,7 +335,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, // invoke settings activity to insert login details if (status == StatusCode.NO_LOGIN_INFO_STORED && fromActivity != null) { - SettingsActivity.jumpToServicesPage(fromActivity); + SettingsActivity.openForScreen(R.string.preference_screen_gc, fromActivity); } } return status == StatusCode.NO_ERROR; @@ -379,17 +398,17 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, public @NonNull List<UserAction> getUserActions() { List<UserAction> actions = super.getUserActions(); - actions.add(new UserAction(R.string.user_menu_open_browser, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_open_browser, new Action1<UserAction.Context>() { @Override - public void run(cgeo.geocaching.connector.UserAction.Context context) { + public void call(cgeo.geocaching.connector.UserAction.Context context) { context.activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + Network.encode(context.userName)))); } })); - actions.add(new UserAction(R.string.user_menu_send_message, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_send_message, new Action1<UserAction.Context>() { @Override - public void run(cgeo.geocaching.connector.UserAction.Context context) { + public void call(cgeo.geocaching.connector.UserAction.Context context) { context.activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/email/?u=" + Network.encode(context.userName)))); } })); @@ -406,4 +425,40 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, return GCParser.searchByUsername(username, Settings.getCacheType(), Settings.isShowCaptcha(), recaptchaReceiver); } + @Override + public boolean uploadFieldNotes(final File exportFile) { + if (!GCLogin.getInstance().isActualLoginStatus()) { + // no need to upload (possibly large file) if we're not logged in + final StatusCode loginState = GCLogin.getInstance().login(); + if (loginState != StatusCode.NO_ERROR) { + Log.e("FieldnoteExport.ExportTask upload: Login failed"); + } + } + + final String uri = "http://www.geocaching.com/my/uploadfieldnotes.aspx"; + final String page = GCLogin.getInstance().getRequestLogged(uri, null); + + if (StringUtils.isBlank(page)) { + Log.e("FieldnoteExport.ExportTask get page: No data from server"); + return false; + } + + final String[] viewstates = GCLogin.getViewstates(page); + + final Parameters uploadParams = new Parameters( + "__EVENTTARGET", "", + "__EVENTARGUMENT", "", + "ctl00$ContentBody$btnUpload", "Upload Field Note"); + + GCLogin.putViewstates(uploadParams, viewstates); + + Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$FieldNoteLoader", "text/plain", exportFile)); + + if (StringUtils.isBlank(page)) { + Log.e("FieldnoteExport.ExportTask upload: No data from server"); + return false; + } + return true; + } + } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index 3900a95..cfc369d 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -1,5 +1,7 @@ package cgeo.geocaching.connector.gc; +import org.eclipse.jdt.annotation.NonNull; + import java.util.Locale; import java.util.regex.Pattern; @@ -15,13 +17,13 @@ public final class GCConstants { static final String GC_URL = "http://www.geocaching.com/"; static final String GC_TILE_URL = "http://tiles.geocaching.com/"; /** Live Map */ - public final static String URL_LIVE_MAP = GC_URL + "map/default.aspx"; + public final static @NonNull String URL_LIVE_MAP = GC_URL + "map/default.aspx"; /** Live Map pop-up */ - public final static String URL_LIVE_MAP_DETAILS = GC_TILE_URL + "map.details"; + public final static @NonNull String URL_LIVE_MAP_DETAILS = GC_TILE_URL + "map.details"; /** Caches in a tile */ - public final static String URL_MAP_INFO = GC_TILE_URL + "map.info"; + public final static @NonNull String URL_MAP_INFO = GC_TILE_URL + "map.info"; /** Tile itself */ - public final static String URL_MAP_TILE = GC_TILE_URL + "map.png"; + public final static @NonNull String URL_MAP_TILE = GC_TILE_URL + "map.png"; /** * Patterns for parsing the result of a (detailed) search diff --git a/main/src/cgeo/geocaching/connector/gc/GCLogin.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java index a7cf6cf..92d488d 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCLogin.java +++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java @@ -14,7 +14,6 @@ import cgeo.geocaching.utils.MatcherWrapper; import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -97,7 +96,7 @@ public class GCLogin extends AbstractLogin { } if (getLoginStatus(loginData)) { - Log.i("Already logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')'); + Log.i("Already logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')'); if (switchToEnglish(loginData) && retry) { return login(false); } @@ -132,7 +131,7 @@ public class GCLogin extends AbstractLogin { assert loginData != null; // Caught above if (getLoginStatus(loginData)) { - Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')'); + Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')'); if (switchToEnglish(loginData) && retry) { return login(false); @@ -204,9 +203,9 @@ public class GCLogin extends AbstractLogin { Log.e("getLoginStatus: bad cache count", e); } setActualCachesFound(cachesCount); - Settings.setMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setGCMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if ( page.contains(GCConstants.MEMBER_STATUS_RENEW) ) { - Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); + Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM); } return true; } @@ -259,9 +258,9 @@ public class GCLogin extends AbstractLogin { final String responseData = StringUtils.defaultString(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/"))); final String profile = TextUtils.replaceWhitespace(responseData); - Settings.setMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setGCMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if (profile.contains(GCConstants.MEMBER_STATUS_RENEW)) { - Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); + Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM); } setActualCachesFound(Integer.parseInt(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index 6c94150..2782b64 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -368,7 +368,7 @@ public class GCMap { } } - if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY) && Settings.isPremiumMember()) { + if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY) && Settings.isGCPremiumMember()) { final Geopoint center = viewport.getCenter(); if ((lastSearchViewport == null) || !lastSearchViewport.contains(center)) { //FIXME We don't have a RecaptchaReceiver!? diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 219adc8..9d6762b 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -36,7 +36,6 @@ import cgeo.geocaching.utils.SynchronizedDateFormat; import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringEscapeUtils; @@ -68,7 +67,7 @@ public abstract class GCParser { private final static SynchronizedDateFormat dateTbIn1 = new SynchronizedDateFormat("EEEEE, dd MMMMM yyyy", Locale.ENGLISH); // Saturday, 28 March 2009 private final static SynchronizedDateFormat dateTbIn2 = new SynchronizedDateFormat("EEEEE, MMMMM dd, yyyy", Locale.ENGLISH); // Saturday, March 28, 2009 - private static SearchResult parseSearch(final String url, final String pageContent, final boolean showCaptcha, RecaptchaReceiver thread) { + private static SearchResult parseSearch(final String url, final String pageContent, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) { if (StringUtils.isBlank(pageContent)) { Log.e("GCParser.parseSearch: No page given"); return null; @@ -86,12 +85,12 @@ public abstract class GCParser { final String recaptchaJsParam = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null); if (recaptchaJsParam != null) { - thread.setKey(recaptchaJsParam.trim()); + recaptchaReceiver.setKey(recaptchaJsParam.trim()); - thread.fetchChallenge(); + recaptchaReceiver.fetchChallenge(); } - if (thread != null && StringUtils.isNotBlank(thread.getChallenge())) { - thread.notifyNeed(); + if (recaptchaReceiver != null && StringUtils.isNotBlank(recaptchaReceiver.getChallenge())) { + recaptchaReceiver.notifyNeed(); } } @@ -277,12 +276,12 @@ public abstract class GCParser { } String recaptchaText = null; - if (thread != null && StringUtils.isNotBlank(thread.getChallenge())) { - thread.waitForUser(); - recaptchaText = thread.getText(); + if (recaptchaReceiver != null && StringUtils.isNotBlank(recaptchaReceiver.getChallenge())) { + recaptchaReceiver.waitForUser(); + recaptchaText = recaptchaReceiver.getText(); } - if (!cids.isEmpty() && (Settings.isPremiumMember() || showCaptcha) && ((thread == null || StringUtils.isBlank(thread.getChallenge())) || StringUtils.isNotBlank(recaptchaText))) { + if (!cids.isEmpty() && (Settings.isGCPremiumMember() || showCaptcha) && ((recaptchaReceiver == null || StringUtils.isBlank(recaptchaReceiver.getChallenge())) || StringUtils.isNotBlank(recaptchaText))) { Log.i("Trying to get .loc for " + cids.size() + " caches"); try { @@ -303,8 +302,8 @@ public abstract class GCParser { params.put("CID", cid); } - if (thread != null && StringUtils.isNotBlank(thread.getChallenge()) && StringUtils.isNotBlank(recaptchaText)) { - params.put("recaptcha_challenge_field", thread.getChallenge()); + if (StringUtils.isNotBlank(recaptchaText) && recaptchaReceiver != null) { + params.put("recaptcha_challenge_field", recaptchaReceiver.getChallenge()); params.put("recaptcha_response_field", recaptchaText); } params.put("ctl00$ContentBody$uxDownloadLoc", "Download Waypoints"); @@ -347,6 +346,9 @@ public abstract class GCParser { // attention: parseCacheFromText already stores implicitly through searchResult.addCache if (searchResult != null && !searchResult.getGeocodes().isEmpty()) { final Geocache cache = searchResult.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); + if (cache == null) { + return null; + } getExtraOnlineInfo(cache, page, handler); // too late: it is already stored through parseCacheFromText cache.setDetailedUpdatedNow(); @@ -741,7 +743,7 @@ public abstract class GCParser { cache.parseWaypointsFromNote(); // logs - cache.setLogs(loadLogsFromDetails(page, cache, false, true)); + cache.setLogs(getLogsFromDetails(page, false)); // last check for necessary cache conditions if (StringUtils.isBlank(cache.getGeocode())) { @@ -1380,8 +1382,7 @@ public abstract class GCParser { } private static boolean changeFavorite(final Geocache cache, final boolean add) { - final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } @@ -1400,6 +1401,11 @@ public abstract class GCParser { return false; } + private static String getUserToken(final Geocache cache) { + final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); + return TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + } + /** * Removes the cache from the favorites. * @@ -1617,19 +1623,20 @@ public abstract class GCParser { } /** - * Load logs from a cache details page. + * Extract logs from a cache details page. * * @param page * the text of the details page - * @param cache - * the cache object to put the logs in * @param friends - * retrieve friend logs + * return friends logs only (will require a network request) + * @return a list of log entries or <code>null</code> if the logs could not be retrieved + * */ - private static List<LogEntry> loadLogsFromDetails(final String page, final Geocache cache, final boolean friends, final boolean getDataFromPage) { + @Nullable + private static List<LogEntry> getLogsFromDetails(final String page, final boolean friends) { String rawResponse; - if (!getDataFromPage) { + if (friends) { final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page); if (!userTokenMatcher.find()) { Log.e("GCParser.loadLogsFromDetails: unable to extract userToken"); @@ -1716,7 +1723,7 @@ public abstract class GCParser { final JSONArray images = entry.getJSONArray("Images"); for (int i = 0; i < images.length(); i++) { final JSONObject image = images.getJSONObject(i); - final String url = "http://img.geocaching.com/cache/log/large/" + image.getString("FileName"); + final String url = "http://imgcdn.geocaching.com/cache/log/large/" + image.getString("FileName"); final String title = TextUtils.removeControlCharacters(image.getString("Name")); final Image logImage = new Image(url, title); logDone.addLogImage(logImage); @@ -1829,7 +1836,7 @@ public abstract class GCParser { if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); final List<LogEntry> allLogs = cache.getLogs(); - final List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); + final List<LogEntry> friendLogs = getLogsFromDetails(page, true); if (friendLogs != null) { for (final LogEntry log : friendLogs) { if (allLogs.contains(log)) { @@ -1864,8 +1871,7 @@ public abstract class GCParser { } public static boolean editModifiedCoordinates(Geocache cache, Geopoint wpt) { - final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } @@ -1900,8 +1906,7 @@ public abstract class GCParser { } public static boolean uploadPersonalNote(Geocache cache) { - final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } diff --git a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java new file mode 100644 index 0000000..ff3f5ef --- /dev/null +++ b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java @@ -0,0 +1,114 @@ +package cgeo.geocaching.connector.gc; + +import cgeo.geocaching.R; +import cgeo.geocaching.loaders.RecaptchaReceiver; +import cgeo.geocaching.network.Network; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.io.IOUtils; + +import rx.Observable; +import rx.android.observables.AndroidObservable; +import rx.schedulers.Schedulers; +import rx.functions.Action1; +import rx.functions.Func0; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; + +import java.io.InputStream; + +public class RecaptchaHandler extends Handler { + final public static int SHOW_CAPTCHA = 1; + + final private Activity activity; + final private RecaptchaReceiver recaptchaReceiver; + + public RecaptchaHandler(final Activity activity, final RecaptchaReceiver recaptchaReceiver) { + this.activity = activity; + this.recaptchaReceiver = recaptchaReceiver; + } + + private void loadChallenge(final ImageView imageView, final View reloadButton) { + getCaptcha().subscribe(new Action1<Bitmap>() { + @Override + public void call(final Bitmap bitmap) { + imageView.setImageBitmap(bitmap); + } + }, new Action1<Throwable>() { + @Override + public void call(final Throwable throwable) { + // Do nothing + } + }); + reloadButton.setEnabled(true); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == SHOW_CAPTCHA) { + final AlertDialog.Builder dlg = new AlertDialog.Builder(activity); + final View view = activity.getLayoutInflater().inflate(R.layout.recaptcha_dialog, null); + + final ImageView imageView = (ImageView) view.findViewById(R.id.image); + + final ImageButton reloadButton = (ImageButton) view.findViewById(R.id.button_recaptcha_refresh); + reloadButton.setEnabled(false); + reloadButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + recaptchaReceiver.fetchChallenge(); + loadChallenge(imageView, reloadButton); + } + }); + + loadChallenge(imageView, reloadButton); + + dlg.setTitle(activity.getString(R.string.caches_recaptcha_title)); + dlg.setView(view); + dlg.setNeutralButton(activity.getString(R.string.caches_recaptcha_continue), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + final String text = ((EditText) view.findViewById(R.id.text)).getText().toString(); + recaptchaReceiver.setText(text); + dialog.cancel(); + } + }); + + dlg.create().show(); + } + } + + private Observable<Bitmap> getCaptcha() { + return AndroidObservable.fromActivity(activity, + Observable.defer(new Func0<Observable<? extends Bitmap>>() { + @Override + public Observable<? extends Bitmap> call() { + final String url = "http://www.google.com/recaptcha/api/image?c=" + recaptchaReceiver.getChallenge(); + final InputStream is = Network.getResponseStream(Network.getRequest(url)); + if (is != null) { + try { + final Bitmap img = BitmapFactory.decodeStream(is); + return Observable.from(img); + } catch (final Exception e) { + Log.e("RecaptchaHandler.getCaptcha", e); + return Observable.error(e); + } finally { + IOUtils.closeQuietly(is); + } + } + return Observable.empty(); + } + }).subscribeOn(Schedulers.io())); + } + +} diff --git a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java b/main/src/cgeo/geocaching/connector/gc/SearchHandler.java deleted file mode 100644 index 795ed2f..0000000 --- a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java +++ /dev/null @@ -1,126 +0,0 @@ -package cgeo.geocaching.connector.gc; - -import cgeo.geocaching.R; -import cgeo.geocaching.loaders.RecaptchaReceiver; -import cgeo.geocaching.utils.Log; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Handler; -import android.os.Message; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; - -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; - -public class SearchHandler extends Handler { - private Activity activity = null; - private Resources res = null; - private RecaptchaReceiver recaptchaThread = null; - private ImageView imageView = null; - private Bitmap img = null; - - private Handler imgHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - try { - if (img != null && imageView != null) { - imageView.setImageBitmap(img); - } - } catch (Exception e) { - Log.e("Error setting reCAPTCHA image", e); - } - } - }; - - public SearchHandler(Activity activityIn, Resources resIn, RecaptchaReceiver recaptchaThreadIn) { - activity = activityIn; - res = resIn; - recaptchaThread = recaptchaThreadIn; - } - - @Override - public void handleMessage(Message msg) { - try { - if (msg.what == 1) { - final AlertDialog.Builder dlg = new AlertDialog.Builder(activity); - final LayoutInflater inflater = activity.getLayoutInflater(); - final View view = inflater.inflate(R.layout.recaptcha_dialog, null); - - imageView = (ImageView) view.findViewById(R.id.image); - - ImageButton reloadButton = (ImageButton) view.findViewById(R.id.button_recaptcha_refresh); - reloadButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - recaptchaThread.fetchChallenge(); - try { - (new GetCaptchaThread(new URL("http://www.google.com/recaptcha/api/image?c=" + recaptchaThread.getChallenge()))).start(); - } catch (MalformedURLException e) { - Log.e("Bad reCAPTCHA image url", e); - } - } - }); - - (new GetCaptchaThread(new URL("http://www.google.com/recaptcha/api/image?c=" + recaptchaThread.getChallenge()))).start(); - - dlg.setTitle(res.getString(R.string.caches_recaptcha_title)); - dlg.setView(view); - dlg.setNeutralButton(res.getString(R.string.caches_recaptcha_continue), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - final String text = ((EditText) view.findViewById(R.id.text)).getText().toString(); - - recaptchaThread.setText(text); - - dialog.cancel(); - } - }); - - dlg.create().show(); - } - } catch (MalformedURLException e) { - Log.e("Error in reCAPTCHA handler", e); - } - } - - private class GetCaptchaThread extends Thread { - private URL uri = null; - - public GetCaptchaThread(URL uriIn) { - uri = uriIn; - } - - @Override - public void run() { - try { - Log.d("Getting reCAPTCHA image from: " + uri.toString()); - HttpURLConnection connection = (HttpURLConnection) uri.openConnection(); - connection.setDoInput(true); - connection.connect(); - - InputStream is = connection.getInputStream(); - - img = BitmapFactory.decodeStream(is); - - is.close(); - - imgHandler.sendEmptyMessage(0); - } catch (IOException e) { - Log.e("Failed to download reCAPTCHA image", e); - } - } - } -} diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java index 623730a..6a257cd 100644 --- a/main/src/cgeo/geocaching/connector/gc/Tile.java +++ b/main/src/cgeo/geocaching/connector/gc/Tile.java @@ -10,6 +10,8 @@ import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; +import org.eclipse.jdt.annotation.NonNull; + import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -110,6 +112,7 @@ public class Tile { * @see <a * href="http://developers.cloudmade.com/projects/tiles/examples/convert-coordinates-to-tile-numbers">Cloudmade</a> */ + @NonNull public Geopoint getCoord(UTFGridPosition pos) { double pixX = tileX * TILE_SIZE + pos.x * 4; @@ -244,7 +247,7 @@ public class Tile { return null; } - public boolean containsPoint(final ICoordinates point) { + public boolean containsPoint(final @NonNull ICoordinates point) { return viewPort.contains(point); } diff --git a/main/src/cgeo/geocaching/connector/oc/IOCAuthParams.java b/main/src/cgeo/geocaching/connector/oc/IOCAuthParams.java index dacb626..acf7b48 100644 --- a/main/src/cgeo/geocaching/connector/oc/IOCAuthParams.java +++ b/main/src/cgeo/geocaching/connector/oc/IOCAuthParams.java @@ -1,5 +1,7 @@ package cgeo.geocaching.connector.oc; +import org.eclipse.jdt.annotation.NonNull; + public interface IOCAuthParams { /** @@ -7,6 +9,7 @@ public interface IOCAuthParams { * * @return */ + @NonNull String getSite(); /** @@ -63,5 +66,6 @@ public interface IOCAuthParams { * * @return */ + @NonNull String getCallbackUri(); } diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java index 46e4c96..284234e 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java @@ -90,4 +90,16 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { public int getTokenSecretPrefKeyId() { return 0; } + + /** + * Checks if a search based on a user name targets the current user + * + * @param username + * Name of the user the query is searching after + * @return True - search target and current is same, False - current user not known or not the same as username + */ + @SuppressWarnings("static-method") + public boolean isSearchForMyCaches(String username) { + return false; + } } diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java index 0b7493c..049c633 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java @@ -173,4 +173,10 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente final Geopoint currentPos = CgeoApplication.getInstance().currentGeo().getCoords(); return new SearchResult(OkapiClient.getCachesNamed(currentPos, name, this)); } + + @Override + public boolean isSearchForMyCaches(String username) { + return StringUtils.equalsIgnoreCase(username, getUserName()); + } + } diff --git a/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java b/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java index c082bac..19f4447 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java +++ b/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java @@ -2,9 +2,13 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; +import cgeo.geocaching.connector.oc.OkapiError.OkapiErrors; import cgeo.geocaching.network.OAuthAuthorizationActivity; import cgeo.geocaching.settings.Settings; +import ch.boye.httpclientandroidlib.HttpResponse; + +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.Nullable; @@ -52,4 +56,16 @@ public abstract class OCAuthorizationActivity extends OAuthAuthorizationActivity return res.getString(R.string.auth_dialog_completed_oc, getAuthTitle()); } + /** + * Return an extended error in case of an invalid time stamp + */ + @Override + protected String getExtendedErrorMsg(HttpResponse response) { + OkapiError error = OkapiClient.decodeErrorResponse(response); + if (error.getResult() == OkapiErrors.INVALID_TIMESTAMP) { + return res.getString(R.string.init_login_popup_invalid_timestamp); + } + return StringUtils.EMPTY; + } + } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 2175935..712bb26 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -34,7 +34,6 @@ 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; @@ -61,7 +60,7 @@ final class OkapiClient { private static final char SEPARATOR = '|'; private static final String SEPARATOR_STRING = Character.toString(SEPARATOR); - private static final FastDateFormat LOG_DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US); + private static final SynchronizedDateFormat LOG_DATE_FORMAT = new SynchronizedDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("UTC"), Locale.US); private static final SynchronizedDateFormat ISO8601DATEFORMAT = new SynchronizedDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()); private static final String CACHE_ATTRNAMES = "attrnames"; @@ -75,7 +74,8 @@ final class OkapiClient { private static final String CACHE_STATUS_ARCHIVED = "Archived"; private static final String CACHE_STATUS_DISABLED = "Temporarily unavailable"; private static final String CACHE_IS_FOUND = "is_found"; - private static final String CACHE_SIZE = "size"; + private static final String CACHE_SIZE_DEPRECATED = "size"; + private static final String CACHE_SIZE2 = "size2"; private static final String CACHE_VOTES = "rating_votes"; private static final String CACHE_NOTFOUNDS = "notfounds"; private static final String CACHE_FOUNDS = "founds"; @@ -112,7 +112,7 @@ final class OkapiClient { // the several realms of possible fields for cache retrieval: // Core: for livemap requests (L3 - only with level 3 auth) // Additional: additional fields for full cache (L3 - only for level 3 auth, current - only for connectors with current api) - private static final String SERVICE_CACHE_CORE_FIELDS = "code|name|location|type|status|difficulty|terrain|size|date_hidden"; + private static final String SERVICE_CACHE_CORE_FIELDS = "code|name|location|type|status|difficulty|terrain|size|size2|date_hidden"; private static final String SERVICE_CACHE_CORE_L3_FIELDS = "is_found"; private static final String SERVICE_CACHE_ADDITIONAL_FIELDS = "owner|founds|notfounds|rating|rating_votes|recommendations|description|hint|images|latest_logs|alt_wpts|attrnames|req_passwd"; private static final String SERVICE_CACHE_ADDITIONAL_CURRENT_FIELDS = "gc_code|attribution_note|attr_acodes"; @@ -148,23 +148,18 @@ final class OkapiClient { valueMap.put("limit", "20"); valueMap.put("radius", "200"); - return requestCaches(connector, params, valueMap); + return requestCaches(connector, params, valueMap, false); } public static List<Geocache> getCachesByOwner(final String username, final OCApiConnector connector) { - final Parameters params = new Parameters("search_method", METHOD_SEARCH_ALL); - final Map<String, String> valueMap = new LinkedHashMap<String, String>(); - final @Nullable - String uuid = getUserUUID(connector, username); - if (StringUtils.isEmpty(uuid)) { - return Collections.emptyList(); - } - valueMap.put("owner_uuid", uuid); - - return requestCaches(connector, params, valueMap); + return getCachesByUser(username, connector, "owner_uuid"); } public static List<Geocache> getCachesByFinder(final String username, final OCApiConnector connector) { + return getCachesByUser(username, connector, "found_by"); + } + + private static List<Geocache> getCachesByUser(final String username, final OCApiConnector connector, final String userRequestParam) { final Parameters params = new Parameters("search_method", METHOD_SEARCH_ALL); final Map<String, String> valueMap = new LinkedHashMap<String, String>(); final @Nullable @@ -172,9 +167,9 @@ final class OkapiClient { if (StringUtils.isEmpty(uuid)) { return Collections.emptyList(); } - valueMap.put("found_by", uuid); + valueMap.put(userRequestParam, uuid); - return requestCaches(connector, params, valueMap); + return requestCaches(connector, params, valueMap, connector.isSearchForMyCaches(username)); } public static List<Geocache> getCachesNamed(final Geopoint center, final String namePart, final OCApiConnector connector) { @@ -195,16 +190,16 @@ final class OkapiClient { // full wildcard search, maybe we need to change this after some testing and evaluation valueMap.put("name", "*" + namePart + "*"); - return requestCaches(connector, params, valueMap); + return requestCaches(connector, params, valueMap, false); } - private static List<Geocache> requestCaches(final OCApiConnector connector, final Parameters params, final Map<String, String> valueMap) { + private static List<Geocache> requestCaches(final OCApiConnector connector, final Parameters params, final Map<String, String> valueMap, final boolean my) { // if a global type filter is set, and OKAPI does not know that type, then return an empty list instead of all caches if (Settings.getCacheType() != CacheType.ALL && StringUtils.isBlank(getFilterFromType())) { return Collections.emptyList(); } - addFilterParams(valueMap, connector); + addFilterParams(valueMap, connector, my); params.add("search_params", new JSONObject(valueMap).toString()); addRetrieveParams(params, connector); @@ -234,7 +229,7 @@ final class OkapiClient { final Map<String, String> valueMap = new LinkedHashMap<String, String>(); valueMap.put("bbox", bboxString); - return requestCaches(connector, params, valueMap); + return requestCaches(connector, params, valueMap, false); } public static boolean setWatchState(final Geocache cache, final boolean watched, final OCApiConnector connector) { @@ -386,6 +381,7 @@ final class OkapiClient { } if (!response.isNull(CACHE_MY_NOTES)) { cache.setPersonalNote(response.getString(CACHE_MY_NOTES)); + cache.parseWaypointsFromNote(); } cache.setLogPasswordRequired(response.getBoolean(CACHE_REQ_PASSWORD)); @@ -489,6 +485,30 @@ final class OkapiClient { if ("Didn't find it".equalsIgnoreCase(logType)) { return LogType.DIDNT_FIND_IT; } + if ("Will attend".equalsIgnoreCase(logType)) { + return LogType.WILL_ATTEND; + } + if ("Attended".equalsIgnoreCase(logType)) { + return LogType.ATTENDED; + } + if ("Temporarily unavailable".equalsIgnoreCase(logType)) { + return LogType.TEMP_DISABLE_LISTING; + } + if ("Ready to search".equalsIgnoreCase(logType)) { + return LogType.ENABLE_LISTING; + } + if ("Archived".equalsIgnoreCase(logType)) { + return LogType.ARCHIVE; + } + if ("Needs maintenance".equalsIgnoreCase(logType)) { + return LogType.NEEDS_MAINTENANCE; + } + if ("Moved".equalsIgnoreCase(logType)) { + return LogType.UPDATE_COORDINATES; + } + if ("OC Team comment".equalsIgnoreCase(logType)) { + return LogType.POST_REVIEWER_NOTE; + } return LogType.NOTE; } @@ -567,12 +587,25 @@ final class OkapiClient { } private static CacheSize getCacheSize(final JSONObject response) { - if (response.isNull(CACHE_SIZE)) { + if (response.isNull(CACHE_SIZE2)) { + return getCacheSizeDeprecated(response); + } + try { + final String size = response.getString(CACHE_SIZE2); + return CacheSize.getById(size); + } catch (JSONException e) { + Log.e("OkapiClient.getCacheSize", e); + return getCacheSizeDeprecated(response); + } + } + + private static CacheSize getCacheSizeDeprecated(final JSONObject response) { + if (response.isNull(CACHE_SIZE_DEPRECATED)) { return CacheSize.NOT_CHOSEN; } double size = 0; try { - size = response.getDouble(CACHE_SIZE); + size = response.getDouble(CACHE_SIZE_DEPRECATED); } catch (final JSONException e) { Log.e("OkapiClient.getCacheSize", e); } @@ -586,7 +619,7 @@ final class OkapiClient { case 4: return CacheSize.LARGE; case 5: - return CacheSize.LARGE; + return CacheSize.VERY_LARGE; default: break; } @@ -687,11 +720,11 @@ final class OkapiClient { return "en"; } - private static void addFilterParams(final Map<String, String> valueMap, final OCApiConnector connector) { + private static void addFilterParams(final Map<String, String> valueMap, final OCApiConnector connector, final boolean my) { if (!Settings.isExcludeDisabledCaches()) { valueMap.put("status", "Available|Temporarily unavailable"); } - if (Settings.isExcludeMyCaches() && connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + if (!my && Settings.isExcludeMyCaches() && connector.getSupportedAuthLevel() == OAuthLevel.Level3) { valueMap.put("exclude_my_own", "true"); valueMap.put("found_status", "notfound_only"); } @@ -789,6 +822,21 @@ final class OkapiClient { } /** + * Retrieves error information from an unsuccessful Okapi-response + * + * @param response + * response containing an error object + * @return OkapiError object with detailed information + */ + public static OkapiError decodeErrorResponse(HttpResponse response) { + final JSONResult result = new JSONResult(response); + if (!result.isSuccess) { + return new OkapiError(result.data); + } + return new OkapiError(new JSONObject()); + } + + /** * 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. */ diff --git a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java index f72b698..5f11a11 100644 --- a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java +++ b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java @@ -28,10 +28,10 @@ public class OXGPXParser extends GPX10Parser { /** * The short description of OX caches contains "title by owner, type(T/D/Awesomeness)". That is a lot of * duplication. - * + * * @param cache */ private static void removeTitleFromShortDescription(final @NonNull Geocache cache) { - cache.setShortDescription(StringUtils.substringAfterLast(cache.getShortDescription(), ",")); + cache.setShortDescription(StringUtils.trim(StringUtils.substringAfterLast(cache.getShortDescription(), ","))); } } diff --git a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java index 2defc52..0137af4 100644 --- a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java +++ b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java @@ -11,7 +11,6 @@ import cgeo.geocaching.utils.CryptUtils; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.collections4.CollectionUtils; import org.eclipse.jdt.annotation.NonNull; diff --git a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java index 69efddc..fb554b9 100644 --- a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.connector.UserAction; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; @@ -15,7 +16,8 @@ public abstract class AbstractTrackableConnector implements TrackableConnector { } @Override - public String getTrackableCodeFromUrl(@NonNull String url) { + public @Nullable + String getTrackableCodeFromUrl(@NonNull String url) { return null; } diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java index 8387076..03052f9 100644 --- a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java @@ -4,6 +4,10 @@ import cgeo.geocaching.Trackable; import cgeo.geocaching.network.Network; import cgeo.geocaching.utils.Log; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + import java.util.regex.Pattern; public class GeokretyConnector extends AbstractTrackableConnector { @@ -29,7 +33,7 @@ public class GeokretyConnector extends AbstractTrackableConnector { return GeokretyParser.parse(page); } - private static int getId(String geocode) { + protected static int getId(String geocode) { try { final String hex = geocode.substring(2); return Integer.parseInt(hex, 16); @@ -39,4 +43,25 @@ public class GeokretyConnector extends AbstractTrackableConnector { return -1; } + @Override + public @Nullable + String getTrackableCodeFromUrl(@NonNull String url) { + // http://geokrety.org/konkret.php?id=38545 + String id = StringUtils.substringAfterLast(url, "konkret.php?id="); + if (StringUtils.isNumeric(id)) { + return geocode(Integer.parseInt(id)); + } + return null; + } + + /** + * Get geocode from geokrety id + * + * @param id + * @return + */ + public static String geocode(final int id) { + return String.format("GK%04X", id); + } + } diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java index ee8c8c0..0e64abd 100644 --- a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java @@ -37,8 +37,8 @@ public class GeokretyParser { public void start(Attributes attributes) { try { final String kretyId = attributes.getValue("id"); - if (StringUtils.isNotBlank(kretyId)) { - trackable.setGeocode(geocode(Integer.parseInt(kretyId))); + if (StringUtils.isNumeric(kretyId)) { + trackable.setGeocode(GeokretyConnector.geocode(Integer.parseInt(kretyId))); } final String distance = attributes.getValue("dist"); if (StringUtils.isNotBlank(distance)) { @@ -88,8 +88,4 @@ public class GeokretyParser { } return null; } - - protected static String geocode(final int id) { - return String.format("GK%04X", id); - } } diff --git a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java index 0990d96..6071b5f 100644 --- a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.Trackable; import cgeo.geocaching.connector.UserAction; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; @@ -21,7 +22,8 @@ public interface TrackableConnector { public Trackable searchTrackable(String geocode, String guid, String id); - public String getTrackableCodeFromUrl(final @NonNull String url); + public @Nullable + String getTrackableCodeFromUrl(final @NonNull String url); public @NonNull List<UserAction> getUserActions(); diff --git a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java index dad285c..77848d7 100644 --- a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java @@ -7,6 +7,7 @@ import cgeo.geocaching.connector.gc.GCParser; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; import java.util.regex.Pattern; @@ -20,7 +21,7 @@ public class TravelBugConnector extends AbstractTrackableConnector { @Override public boolean canHandleTrackable(String geocode) { - return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches(); + return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches() && !StringUtils.startsWithIgnoreCase(geocode, "GC"); } @Override @@ -54,8 +55,18 @@ public class TravelBugConnector extends AbstractTrackableConnector { } @Override - public String getTrackableCodeFromUrl(@NonNull String url) { - return StringUtils.substringAfterLast(url, "?tracker="); + public @Nullable + String getTrackableCodeFromUrl(@NonNull String url) { + // coord.info URLs + String code = StringUtils.substringAfterLast(url, "coord.info/"); + if (code != null && canHandleTrackable(code)) { + return code; + } + code = StringUtils.substringAfterLast(url, "?tracker="); + if (code != null && canHandleTrackable(code)) { + return code; + } + return null; } @Override |
