diff options
Diffstat (limited to 'main/src/cgeo/geocaching/connector')
20 files changed, 302 insertions, 226 deletions
diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index 53a3bcb..ca05439 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.util.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..c6cfeb5 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.util.functions.Func1; +import rx.util.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..da03a95 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.util.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/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..a62b1f6 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -27,12 +27,12 @@ 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.util.functions.Action1; import android.content.Context; import android.content.Intent; @@ -91,7 +91,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override public boolean supportsPersonalNote() { - return Settings.isPremiumMember(); + return Settings.isGCPremiumMember(); } @Override @@ -285,7 +285,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 +331,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 +394,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)))); } })); 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 62ccb14..f60764d 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. * @@ -1613,19 +1619,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"); @@ -1712,7 +1719,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); @@ -1825,7 +1832,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)) { @@ -1860,8 +1867,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; } @@ -1896,8 +1902,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..44ad850 --- /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.util.functions.Action1; +import rx.util.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/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 2175935..3ae4e97 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"; @@ -152,19 +152,14 @@ final class OkapiClient { } 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,7 +167,7 @@ final class OkapiClient { if (StringUtils.isEmpty(uuid)) { return Collections.emptyList(); } - valueMap.put("found_by", uuid); + valueMap.put(userRequestParam, uuid); return requestCaches(connector, params, valueMap); } @@ -567,12 +562,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 +594,7 @@ final class OkapiClient { case 4: return CacheSize.LARGE; case 5: - return CacheSize.LARGE; + return CacheSize.VERY_LARGE; default: break; } 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 |
