package cgeo.geocaching.connector.gc; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; import cgeo.geocaching.LogCacheActivity; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.connector.ILoggingManager; import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByGeocode; import cgeo.geocaching.connector.capability.ISearchByViewPort; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.settings.SettingsActivity; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import android.content.Context; import android.os.Handler; import java.util.regex.Pattern; public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort, ILogin { private static final String CACHE_URL_SHORT = "http://coord.info/"; // Double slash is used to force open in browser private static final String CACHE_URL_LONG = "http://www.geocaching.com//seek/cache_details.aspx?wp="; /** * Pocket queries downloaded from the website use a numeric prefix. The pocket query creator Android app adds a * verbatim "pocketquery" prefix. */ private static final Pattern GPX_ZIP_FILE_PATTERN = Pattern.compile("((\\d{7,})|(pocketquery))" + "(_.+)?" + "\\.zip", Pattern.CASE_INSENSITIVE); /** * Pattern for GC codes */ private final static Pattern PATTERN_GC_CODE = Pattern.compile("GC[0-9A-Z]+", Pattern.CASE_INSENSITIVE); private GCConnector() { // singleton } /** * initialization on demand holder pattern */ private static class Holder { private static final GCConnector INSTANCE = new GCConnector(); } public static GCConnector getInstance() { return Holder.INSTANCE; } @Override public boolean canHandle(String geocode) { if (geocode == null) { return false; } return GCConnector.PATTERN_GC_CODE.matcher(geocode).matches(); } @Override public String getLongCacheUrl(Geocache cache) { return CACHE_URL_LONG + cache.getGeocode(); } @Override public String getCacheUrl(Geocache cache) { return CACHE_URL_SHORT + cache.getGeocode(); } @Override public boolean supportsPersonalNote() { return true; } @Override public boolean supportsOwnCoordinates() { return true; } @Override public boolean supportsWatchList() { return true; } @Override public boolean supportsLogging() { return true; } @Override public boolean supportsLogImages() { return true; } @Override public ILoggingManager getLoggingManager(final LogCacheActivity activity, final Geocache cache) { return new GCLoggingManager(activity, cache); } @Override public boolean canLog(Geocache cache) { return StringUtils.isNotBlank(cache.getCacheId()); } @Override public String getName() { return "geocaching.com"; } @Override public String getHost() { return "www.geocaching.com"; } @Override public boolean supportsUserActions() { return true; } @Override public SearchResult searchByGeocode(final String geocode, final String guid, final CancellableHandler handler) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_loadpage); final String page = GCParser.requestHtmlPage(geocode, guid, "y", String.valueOf(GCConstants.NUMBER_OF_LOGS)); if (StringUtils.isEmpty(page)) { final SearchResult search = new SearchResult(); if (DataStore.isThere(geocode, guid, true, false)) { if (StringUtils.isBlank(geocode) && StringUtils.isNotBlank(guid)) { Log.i("Loading old cache from cache."); search.addGeocode(DataStore.getGeocodeForGuid(guid)); } else { search.addGeocode(geocode); } search.setError(StatusCode.NO_ERROR); return search; } Log.e("GCConnector.searchByGeocode: No data from server"); search.setError(StatusCode.COMMUNICATION_ERROR); return search; } assert page != null; final SearchResult searchResult = GCParser.parseCache(page, handler); if (searchResult == null || CollectionUtils.isEmpty(searchResult.getGeocodes())) { Log.w("GCConnector.searchByGeocode: No cache parsed"); return searchResult; } // do not filter when searching for one specific cache return searchResult; } @Override public SearchResult searchByViewport(Viewport viewport, String[] tokens) { return GCMap.searchByViewport(viewport, tokens); } @Override public boolean isZippedGPXFile(final String fileName) { return GPX_ZIP_FILE_PATTERN.matcher(fileName).matches(); } @Override public boolean isReliableLatLon(boolean cacheHasReliableLatLon) { return cacheHasReliableLatLon; } @Override public boolean isOwner(final ICache cache) { return StringUtils.equalsIgnoreCase(cache.getOwnerUserId(), Settings.getUsername()); } @Override public boolean addToWatchlist(Geocache cache) { final boolean added = GCParser.addToWatchlist(cache); if (added) { DataStore.saveChangedCache(cache); } return added; } @Override public boolean removeFromWatchlist(Geocache cache) { final boolean removed = GCParser.removeFromWatchlist(cache); if (removed) { DataStore.saveChangedCache(cache); } return removed; } /** * Add a cache to the favorites list. * * This must not be called from the UI thread. * * @param cache the cache to add * @return true if the cache was sucessfully added, false otherwise */ public static boolean addToFavorites(Geocache cache) { final boolean added = GCParser.addToFavorites(cache); if (added) { DataStore.saveChangedCache(cache); } return added; } /** * Remove a cache from the favorites list. * * This must not be called from the UI thread. * * @param cache the cache to add * @return true if the cache was sucessfully added, false otherwise */ public static boolean removeFromFavorites(Geocache cache) { final boolean removed = GCParser.removeFromFavorites(cache); if (removed) { DataStore.saveChangedCache(cache); } return removed; } @Override public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt) { final boolean uploaded = GCParser.uploadModifiedCoordinates(cache, wpt); if (uploaded) { DataStore.saveChangedCache(cache); } return uploaded; } @Override public boolean deleteModifiedCoordinates(Geocache cache) { final boolean deleted = GCParser.deleteModifiedCoordinates(cache); if (deleted) { DataStore.saveChangedCache(cache); } return deleted; } @Override public boolean uploadPersonalNote(Geocache cache) { final boolean uploaded = GCParser.uploadPersonalNote(cache); if (uploaded) { DataStore.saveChangedCache(cache); } return uploaded; } @Override public SearchResult searchByCenter(Geopoint center) { // TODO make search by coordinate use this method. currently it is just a marker that this connector supports search by center return null; } @Override public boolean supportsFavoritePoints() { return true; } @Override protected String getCacheUrlPrefix() { return CACHE_URL_SHORT; } @Override public boolean isActivated() { return Settings.isGCConnectorActive(); } @Override public int getCacheMapMarkerId(boolean disabled) { if (disabled) { return R.drawable.marker_disabled; } return R.drawable.marker; } @Override public boolean login(Handler handler, Context fromActivity) { // login final StatusCode status = Login.login(); if (status == StatusCode.NO_ERROR) { CgeoApplication.getInstance().checkLogin = false; Login.detectGcCustomDate(); } if (CgeoApplication.getInstance().showLoginToast && handler != null) { handler.sendMessage(handler.obtainMessage(0, status)); CgeoApplication.getInstance().showLoginToast = false; // invoke settings activity to insert login details if (status == StatusCode.NO_LOGIN_INFO_STORED && fromActivity != null) { SettingsActivity.jumpToServicesPage(fromActivity); } } return status == StatusCode.NO_ERROR; } @Override public String getUserName() { return Login.getActualUserName(); } @Override public int getCachesFound() { return Login.getActualCachesFound(); } @Override public String getLoginStatusString() { return Login.getActualStatus(); } @Override public boolean isLoggedIn() { return Login.isActualLoginStatus(); } }