diff options
Diffstat (limited to 'main/src/cgeo/geocaching/connector')
38 files changed, 2384 insertions, 1330 deletions
diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index 413291c..28ad12b 100644 --- a/main/src/cgeo/geocaching/connector/AbstractConnector.java +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -1,11 +1,13 @@ package cgeo.geocaching.connector; import cgeo.geocaching.Geocache; -import cgeo.geocaching.enumerations.CacheRealm; +import cgeo.geocaching.R; import cgeo.geocaching.geopoint.Geopoint; import org.apache.commons.lang3.StringUtils; +import android.app.Activity; + public abstract class AbstractConnector implements IConnector { @Override @@ -19,17 +21,30 @@ public abstract class AbstractConnector implements IConnector { } @Override + public boolean addToWatchlist(Geocache cache) { + return false; + } + + @Override + public boolean removeFromWatchlist(Geocache cache) { + return false; + } + + @Override + public boolean supportsPersonalNote() { + return false; + } + + @Override + public boolean uploadPersonalNote(Geocache cache) { + throw new UnsupportedOperationException(); + } + + @Override public boolean supportsOwnCoordinates() { return false; } - /** - * Uploading modified coordinates to website - * - * @param cache - * @param wpt - * @return success - */ @Override public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt) { throw new UnsupportedOperationException(); @@ -54,6 +69,21 @@ public abstract class AbstractConnector implements IConnector { } @Override + public boolean supportsLogImages() { + return false; + } + + @Override + public boolean canLog(Geocache cache) { + return false; + } + + @Override + public ILoggingManager getLoggingManager(Activity activity, Geocache cache) { + return new NoLoggingManager(); + } + + @Override public String getLicenseText(final Geocache cache) { return null; } @@ -84,11 +114,6 @@ public abstract class AbstractConnector implements IConnector { } @Override - public String[] getTokens() { - return null; - } - - @Override public String getGeocodeFromUrl(final String url) { final String urlPrefix = getCacheUrlPrefix(); if (StringUtils.startsWith(url, urlPrefix)) { @@ -111,15 +136,15 @@ public abstract class AbstractConnector implements IConnector { * {@link IConnector} */ @Override - public CacheRealm getCacheRealm() { - return CacheRealm.OTHER; + public boolean isActivated() { + return false; } - /** - * {@link IConnector} - */ @Override - public boolean isActivated() { - return false; + public int getCacheMapMarkerId(boolean disabled) { + if (disabled) { + return R.drawable.marker_disabled_other; + } + return R.drawable.marker_other; } } diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index 561bae2..eb09978 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -1,15 +1,22 @@ package cgeo.geocaching.connector; import cgeo.geocaching.ICache; +import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Trackable; +import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByViewPort; import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.oc.OCApiConnector; +import cgeo.geocaching.connector.oc.OCApiConnector.ApiSupport; +import cgeo.geocaching.connector.oc.OCApiLiveConnector; import cgeo.geocaching.connector.oc.OCConnector; -import cgeo.geocaching.connector.oc.OCXMLApiConnector; import cgeo.geocaching.connector.ox.OXConnector; +import cgeo.geocaching.connector.trackable.GeokretyConnector; +import cgeo.geocaching.connector.trackable.TrackableConnector; +import cgeo.geocaching.connector.trackable.TravelBugConnector; +import cgeo.geocaching.connector.trackable.UnknownTrackableConnector; import cgeo.geocaching.geopoint.Viewport; import org.apache.commons.lang3.StringUtils; @@ -19,39 +26,47 @@ import java.util.List; public final class ConnectorFactory { private static final UnknownConnector UNKNOWN_CONNECTOR = new UnknownConnector(); - private static final IConnector[] connectors = new IConnector[] { + private static final IConnector[] CONNECTORS = new IConnector[] { GCConnector.getInstance(), - new OCXMLApiConnector("OpenCaching.DE", "www.opencaching.de", "OC"), + new OCApiLiveConnector("opencaching.de", "www.opencaching.de", "OC", R.string.oc_de_okapi_consumer_key, R.string.oc_de_okapi_consumer_secret, ApiSupport.current), new OCConnector("OpenCaching.CZ", "www.opencaching.cz", "OZ"), - new OCApiConnector("OpenCaching.CO.UK", "www.opencaching.org.uk", "OK", "arU4okouc4GEjMniE2fq"), + new OCApiConnector("OpenCaching.CO.UK", "www.opencaching.org.uk", "OK", "arU4okouc4GEjMniE2fq", ApiSupport.oldapi), new OCConnector("OpenCaching.ES", "www.opencachingspain.es", "OC"), new OCConnector("OpenCaching.IT", "www.opencaching.it", "OC"), new OCConnector("OpenCaching.JP", "www.opencaching.jp", "OJ"), new OCConnector("OpenCaching.NO/SE", "www.opencaching.se", "OS"), - new OCApiConnector("OpenCaching.NL", "www.opencaching.nl", "OB", "PdzU8jzIlcfMADXaYN8j"), - new OCApiConnector("OpenCaching.PL", "www.opencaching.pl", "OP", "GkxM47WkUkLQXXsZ9qSh"), - new OCApiConnector("OpenCaching.US", "www.opencaching.us", "OU", "pTsYAYSXFcfcRQnYE6uA"), + new OCApiConnector("OpenCaching.NL", "www.opencaching.nl", "OB", "PdzU8jzIlcfMADXaYN8j", ApiSupport.current), + new OCApiConnector("OpenCaching.PL", "www.opencaching.pl", "OP", "GkxM47WkUkLQXXsZ9qSh", ApiSupport.current), + new OCApiConnector("OpenCaching.US", "www.opencaching.us", "OU", "pTsYAYSXFcfcRQnYE6uA", ApiSupport.oldapi), new OXConnector(), new GeocachingAustraliaConnector(), new GeopeitusConnector(), + new WaymarkingConnector(), UNKNOWN_CONNECTOR // the unknown connector MUST be the last one }; + public static final UnknownTrackableConnector UNKNOWN_TRACKABLE_CONNECTOR = new UnknownTrackableConnector(); + private static final TrackableConnector[] TRACKABLE_CONNECTORS = new TrackableConnector[] { + new GeokretyConnector(), // GK must be first, as it overlaps with the secret codes of travel bugs + TravelBugConnector.getInstance(), + UNKNOWN_TRACKABLE_CONNECTOR // must be last + }; + private static final ISearchByViewPort[] searchByViewPortConns; private static final ISearchByCenter[] searchByCenterConns; static { - List<ISearchByViewPort> vpConns = new ArrayList<ISearchByViewPort>(); - for (IConnector conn : connectors) { + final List<ISearchByViewPort> vpConns = new ArrayList<ISearchByViewPort>(); + for (final IConnector conn : CONNECTORS) { if (conn instanceof ISearchByViewPort) { vpConns.add((ISearchByViewPort) conn); } } searchByViewPortConns = vpConns.toArray(new ISearchByViewPort[vpConns.size()]); - List<ISearchByCenter> centerConns = new ArrayList<ISearchByCenter>(); - for (IConnector conn : connectors) { + final List<ISearchByCenter> centerConns = new ArrayList<ISearchByCenter>(); + for (final IConnector conn : CONNECTORS) { // GCConnector is handled specially, omit it here! if (conn instanceof ISearchByCenter && !(conn instanceof GCConnector)) { centerConns.add((ISearchByCenter) conn); @@ -61,18 +76,28 @@ public final class ConnectorFactory { } public static IConnector[] getConnectors() { - return connectors; + return CONNECTORS; } public static ISearchByCenter[] getSearchByCenterConnectors() { return searchByCenterConns; } + public static ILogin[] getActiveLiveConnectors() { + final List<ILogin> liveConns = new ArrayList<ILogin>(); + for (final IConnector conn : CONNECTORS) { + if (conn instanceof ILogin && conn.isActivated()) { + liveConns.add((ILogin) conn); + } + } + return liveConns.toArray(new ILogin[liveConns.size()]); + } + public static boolean canHandle(final String geocode) { if (isInvalidGeocode(geocode)) { return false; } - for (IConnector connector : connectors) { + for (final IConnector connector : CONNECTORS) { if (connector.canHandle(geocode)) { return true; } @@ -84,8 +109,17 @@ public final class ConnectorFactory { return getConnector(cache.getGeocode()); } - public static IConnector getConnector(Trackable trackable) { - return getConnector(trackable.getGeocode()); + public static TrackableConnector getConnector(Trackable trackable) { + return getTrackableConnector(trackable.getGeocode()); + } + + public static TrackableConnector getTrackableConnector(String geocode) { + for (final TrackableConnector connector : TRACKABLE_CONNECTORS) { + if (connector.canHandleTrackable(geocode)) { + return connector; + } + } + return UNKNOWN_TRACKABLE_CONNECTOR; // avoid null checks by returning a non implementing connector } public static IConnector getConnector(final String geocodeInput) { @@ -94,12 +128,12 @@ public final class ConnectorFactory { if (isInvalidGeocode(geocode)) { return UNKNOWN_CONNECTOR; } - for (IConnector connector : connectors) { + for (final IConnector connector : CONNECTORS) { if (connector.canHandle(geocode)) { return connector; } } - // in case of errors, take UNKNOWN + // in case of errors, take UNKNOWN to avoid null checks everywhere return UNKNOWN_CONNECTOR; } @@ -110,21 +144,18 @@ public final class ConnectorFactory { /** @see ISearchByViewPort#searchByViewport */ public static SearchResult searchByViewport(final Viewport viewport, final String[] tokens) { - SearchResult result = new SearchResult(); - for (ISearchByViewPort vpconn : searchByViewPortConns) { + final SearchResult result = new SearchResult(); + for (final ISearchByViewPort vpconn : searchByViewPortConns) { if (vpconn.isActivated()) { - SearchResult temp = vpconn.searchByViewport(viewport, tokens); - if (temp != null) { - result.addGeocodes(temp.getGeocodes()); - } + result.addSearchResult(vpconn.searchByViewport(viewport, tokens)); } } return result; } public static String getGeocodeFromURL(final String url) { - for (IConnector connector : connectors) { - String geocode = connector.getGeocodeFromUrl(url); + for (final IConnector connector : CONNECTORS) { + final String geocode = connector.getGeocodeFromUrl(url); if (StringUtils.isNotBlank(geocode)) { return geocode; } @@ -132,4 +163,8 @@ public final class ConnectorFactory { return null; } + public static TrackableConnector[] getTrackableConnectors() { + return TRACKABLE_CONNECTORS; + } + } diff --git a/main/src/cgeo/geocaching/connector/IConnector.java b/main/src/cgeo/geocaching/connector/IConnector.java index 9169b4a..0c175cd 100644 --- a/main/src/cgeo/geocaching/connector/IConnector.java +++ b/main/src/cgeo/geocaching/connector/IConnector.java @@ -2,9 +2,10 @@ package cgeo.geocaching.connector; import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; -import cgeo.geocaching.enumerations.CacheRealm; import cgeo.geocaching.geopoint.Geopoint; +import android.app.Activity; + public interface IConnector { /** * get name for display (currently only used in links) @@ -45,6 +46,22 @@ public interface IConnector { public boolean supportsWatchList(); /** + * Add the cache to the watchlist + * + * @param cache + * @return True - success/False - failure + */ + public boolean addToWatchlist(Geocache cache); + + /** + * Remove the cache from the watchlist + * + * @param cache + * @return True - success/False - failure + */ + public boolean removeFromWatchlist(Geocache cache); + + /** * enable/disable favorite points controls in cache details * * @return @@ -59,6 +76,20 @@ public interface IConnector { public boolean supportsLogging(); /** + * enable/disable attaching image to log + * + * @return + */ + public boolean supportsLogImages(); + + /** + * Get an ILoggingManager to guide the logging process. + * + * @return + */ + public ILoggingManager getLoggingManager(Activity activity, Geocache cache); + + /** * get host name of the connector server for dynamic loading of data * * @return @@ -98,13 +129,6 @@ public interface IConnector { public boolean isReliableLatLon(boolean cacheHasReliableLatLon); /** - * Return required tokens for specific following actions - * - * @return - */ - public String[] getTokens(); - - /** * extract a geocode from the given URL, if this connector can handle that URL somehow * * @param url @@ -113,23 +137,29 @@ public interface IConnector { public String getGeocodeFromUrl(final String url); /** - * enable/disable uploading modified coordinates to website + * enable/disable uploading personal note * * @return true, when uploading is possible */ - public boolean supportsOwnCoordinates(); + public boolean supportsPersonalNote(); /** - * Uploading modified coordinates to website + * Uploading personal note to website * * @param cache - * @param wpt * @return success */ - public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt); + public boolean uploadPersonalNote(Geocache cache); /** - * Reseting of modified coordinates on website to details + * enable/disable uploading modified coordinates to website + * + * @return true, when uploading is possible + */ + public boolean supportsOwnCoordinates(); + + /** + * Resetting of modified coordinates on website to details * * @param cache * @return success @@ -137,11 +167,13 @@ public interface IConnector { public boolean deleteModifiedCoordinates(Geocache cache); /** - * The CacheRealm this cache belongs to + * Uploading modified coordinates to website * - * @return + * @param cache + * @param wpt + * @return success */ - public CacheRealm getCacheRealm(); + public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt); /** * Return true if this connector is activated for online @@ -159,4 +191,22 @@ public interface IConnector { * @return <code>true</code> if the current user is the cache owner, <code>false</code> otherwise */ public boolean isOwner(final ICache cache); + + /** + * Check if the cache information is complete enough to be + * able to log online. + * + * @param geocache + * @return + */ + public boolean canLog(Geocache geocache); + + /** + * Return the marker id of the caches for this connector. This creates the different backgrounds for cache markers + * on the map. + * + * @param disabled + * Whether to return the enabled or disabled marker type + */ + public int getCacheMapMarkerId(boolean disabled); } diff --git a/main/src/cgeo/geocaching/connector/ILoggingManager.java b/main/src/cgeo/geocaching/connector/ILoggingManager.java new file mode 100644 index 0000000..c5586b3 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ILoggingManager.java @@ -0,0 +1,45 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.TrackableLog; +import cgeo.geocaching.enumerations.LogType; + +import android.net.Uri; + +import java.util.Calendar; +import java.util.List; + +public interface ILoggingManager { + + /** + * Post a log for a cache online + * + * @param cache + * @param logType + * @param date + * @param log + * @param logPassword + * optional, maybe null + * @param trackableLogs + * @return + */ + LogResult postLog(Geocache cache, + LogType logType, + Calendar date, + String log, + String logPassword, + List<TrackableLog> trackableLogs); + + ImageResult postLogImage(String logId, + String imageCaption, + String imageDescription, + Uri imageUri); + + public boolean hasLoaderError(); + + public List<TrackableLog> getTrackables(); + + public List<LogType> getPossibleLogTypes(); + + public void init(); +} diff --git a/main/src/cgeo/geocaching/connector/ImageResult.java b/main/src/cgeo/geocaching/connector/ImageResult.java new file mode 100644 index 0000000..9314cad --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ImageResult.java @@ -0,0 +1,23 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.enumerations.StatusCode; + +public class ImageResult { + + private final StatusCode postResult; + private final String imageUri; + + public ImageResult(StatusCode postResult, String imageUri) { + this.postResult = postResult; + this.imageUri = imageUri; + } + + public StatusCode getPostResult() { + return postResult; + } + + public String getImageUri() { + return imageUri; + } + +} diff --git a/main/src/cgeo/geocaching/connector/LogResult.java b/main/src/cgeo/geocaching/connector/LogResult.java new file mode 100644 index 0000000..62111a4 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/LogResult.java @@ -0,0 +1,23 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.enumerations.StatusCode; + +public class LogResult { + + private final StatusCode postLogResult; + private final String logId; + + public LogResult(StatusCode postLogResult, String logId) { + this.postLogResult = postLogResult; + this.logId = logId; + } + + public StatusCode getPostLogResult() { + return postLogResult; + } + + public String getLogId() { + return logId; + } + +} diff --git a/main/src/cgeo/geocaching/connector/NoLoggingManager.java b/main/src/cgeo/geocaching/connector/NoLoggingManager.java new file mode 100644 index 0000000..04a73c1 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/NoLoggingManager.java @@ -0,0 +1,46 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.TrackableLog; +import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; + +import android.net.Uri; + +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +public class NoLoggingManager implements ILoggingManager { + + @Override + public void init() { + // nothing to do + } + + @Override + public LogResult postLog(Geocache cache, LogType logType, Calendar date, String log, String logPassword, List<TrackableLog> trackableLogs) { + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } + + @Override + public ImageResult postLogImage(String logId, String imageCaption, String imageDescription, Uri imageUri) { + return new ImageResult(StatusCode.LOG_POST_ERROR, ""); + } + + @Override + public boolean hasLoaderError() { + return true; + } + + @Override + public List<TrackableLog> getTrackables() { + return Collections.emptyList(); + } + + @Override + public List<LogType> getPossibleLogTypes() { + return Collections.emptyList(); + } + +} diff --git a/main/src/cgeo/geocaching/connector/UnknownConnector.java b/main/src/cgeo/geocaching/connector/UnknownConnector.java index b6fc29a..e9fecb9 100644 --- a/main/src/cgeo/geocaching/connector/UnknownConnector.java +++ b/main/src/cgeo/geocaching/connector/UnknownConnector.java @@ -1,7 +1,7 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.ICache; import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; import org.apache.commons.lang3.StringUtils; @@ -36,4 +36,5 @@ public class UnknownConnector extends AbstractConnector { protected String getCacheUrlPrefix() { return null; } + } diff --git a/main/src/cgeo/geocaching/connector/WaymarkingConnector.java b/main/src/cgeo/geocaching/connector/WaymarkingConnector.java new file mode 100644 index 0000000..f184f6e --- /dev/null +++ b/main/src/cgeo/geocaching/connector/WaymarkingConnector.java @@ -0,0 +1,40 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; + +import org.apache.commons.lang3.StringUtils; + +public class WaymarkingConnector extends AbstractConnector { + + @Override + public String getName() { + return "Waymarking"; + } + + @Override + public String getCacheUrl(Geocache cache) { + return getCacheUrlPrefix() + cache.getGeocode(); + } + + @Override + public String getHost() { + return "www.waymarking.com"; + } + + @Override + public boolean isOwner(ICache cache) { + // this connector has no user management + return false; + } + + @Override + protected String getCacheUrlPrefix() { + return "http://" + getHost() + "/waymarks/"; + } + + @Override + public boolean canHandle(String geocode) { + return StringUtils.startsWith(geocode, "WM"); + } +} diff --git a/main/src/cgeo/geocaching/connector/capability/ILogin.java b/main/src/cgeo/geocaching/connector/capability/ILogin.java new file mode 100644 index 0000000..4a839c8 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/capability/ILogin.java @@ -0,0 +1,57 @@ +package cgeo.geocaching.connector.capability; + +import cgeo.geocaching.connector.IConnector; + +import android.content.Context; +import android.os.Handler; + +public interface ILogin extends IConnector { + + + /** + * Contacts the server the connector belongs to + * and verifies/establishes authentication and + * retrieves information about the current user + * (Name, found caches) if applicable. + * + * @param handler + * Handler to receive status feedback + * @param fromActivity + * Calling activity context + * @return true in case of success, false in case of failure + */ + boolean login(Handler handler, Context fromActivity); + + /** + * Returns the status of the last {@link}login() request + * + * @return + */ + boolean isLoggedIn(); + + /** + * User-centered string describing the current login/connection status + * + * @return + */ + String getLoginStatusString(); + + /** + * Name the user has in this connector or empty string if not applicable + * It might be necessary to execute login before this information is valid. + * + * @return + */ + String getUserName(); + + /** + * Number of caches the user has found in this connector + * Normally retrieved/updated with (@see login). + * Might be out dated as changes on the connectors site + * are generally not notified. + * + * @return + */ + int getCachesFound(); + +} diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index 50bf096..44d6e8f 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -4,30 +4,46 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.Settings; import cgeo.geocaching.cgData; +import cgeo.geocaching.cgeoapplication; 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.CacheRealm; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.settings.SettingsActivity; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import android.app.Activity; +import android.content.Context; +import android.os.Handler; + import java.util.regex.Pattern; -public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort { +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="; - private static final Pattern gpxZipFilePattern = Pattern.compile("\\d{7,}(_.+)?\\.zip", Pattern.CASE_INSENSITIVE); + /** + * 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 @@ -49,7 +65,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, if (geocode == null) { return false; } - return GCConstants.PATTERN_GC_CODE.matcher(geocode).matches() || GCConstants.PATTERN_TB_CODE.matcher(geocode).matches(); + return GCConnector.PATTERN_GC_CODE.matcher(geocode).matches(); } @Override @@ -63,6 +79,11 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override + public boolean supportsPersonalNote() { + return true; + } + + @Override public boolean supportsOwnCoordinates() { return true; } @@ -78,8 +99,23 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override + public boolean supportsLogImages() { + return true; + } + + @Override + public ILoggingManager getLoggingManager(Activity activity, 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"; + return "geocaching.com"; } @Override @@ -135,7 +171,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override public boolean isZippedGPXFile(final String fileName) { - return gpxZipFilePattern.matcher(fileName).matches(); + return GPX_ZIP_FILE_PATTERN.matcher(fileName).matches(); } @Override @@ -149,7 +185,8 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } - public static boolean addToWatchlist(Geocache cache) { + @Override + public boolean addToWatchlist(Geocache cache) { final boolean added = GCParser.addToWatchlist(cache); if (added) { cgData.saveChangedCache(cache); @@ -157,7 +194,8 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, return added; } - public static boolean removeFromWatchlist(Geocache cache) { + @Override + public boolean removeFromWatchlist(Geocache cache) { final boolean removed = GCParser.removeFromWatchlist(cache); if (removed) { cgData.saveChangedCache(cache); @@ -218,6 +256,15 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override + public boolean uploadPersonalNote(Geocache cache) { + final boolean uploaded = GCParser.uploadPersonalNote(cache); + if (uploaded) { + cgData.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; @@ -234,12 +281,57 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override - public CacheRealm getCacheRealm() { - return CacheRealm.GC; + public boolean isActivated() { + return Settings.isGCConnectorActive(); } @Override - public boolean isActivated() { - return true; + 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().firstRun = 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.startWithServicesPage(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(); } } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index b66acb7..d908b47 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -36,7 +36,7 @@ public final class GCConstants { public final static Pattern PATTERN_LATLON = Pattern.compile("<span id=\"uxLatLon\"[^>]*>(.*?)</span>"); public final static Pattern PATTERN_LATLON_ORIG = Pattern.compile("\\{\"isUserDefined\":true[^}]+?\"oldLatLngDisplay\":\"([^\"]+)\"\\}"); public final static Pattern PATTERN_LOCATION = Pattern.compile(Pattern.quote("<span id=\"ctl00_ContentBody_Location\">In ") + "(?:<a href=[^>]*>)?(.*?)<"); - public final static Pattern PATTERN_PERSONALNOTE = Pattern.compile("<p id=\"cache_note\"[^>]*>(.*?)</p>"); + public final static Pattern PATTERN_PERSONALNOTE = Pattern.compile("<p id=\"cache_note\"[^>]*>(.*?)</p>", Pattern.DOTALL); public final static Pattern PATTERN_NAME = Pattern.compile("<span id=\"ctl00_ContentBody_CacheName\">(.*?)</span>"); public final static Pattern PATTERN_DIFFICULTY = Pattern.compile("<span id=\"ctl00_ContentBody_uxLegendScale\"[^>]*>[^<]*<img src=\"[^\"]*/images/stars/stars([0-9_]+)\\.gif\""); public final static Pattern PATTERN_TERRAIN = Pattern.compile("<span id=\"ctl00_ContentBody_Localize[\\d]+\"[^>]*>[^<]*<img src=\"[^\"]*/images/stars/stars([0-9_]+)\\.gif\""); @@ -69,7 +69,7 @@ public final class GCConstants { public static final String MEMBER_STATUS_RENEW = "<a id=\"ctl00_hlRenew"; public static final String MEMBER_STATUS_PM = "Premium Member"; /** Use replaceAll("[,.]","") on the resulting string before converting to an int */ - public static final Pattern PATTERN_CACHES_FOUND = Pattern.compile("<strong[^>]*>.*?([\\d,.]+) Caches Found"); + public static final Pattern PATTERN_CACHES_FOUND = Pattern.compile("<strong[^>]*>.*?([\\d,.]+) Caches Found", Pattern.DOTALL); public static final Pattern PATTERN_AVATAR_IMAGE_PROFILE_PAGE = Pattern.compile("<img src=\"(http://img.geocaching.com/user/avatar/[0-9a-f-]+\\.jpg)\"[^>]*\\salt=\"Avatar\""); public static final Pattern PATTERN_LOGIN_NAME_LOGIN_PAGE = Pattern.compile("ctl00_ContentBody_lbUsername\">.*<strong>(.*)</strong>"); public static final Pattern PATTERN_CUSTOMDATE = Pattern.compile("<option selected=\"selected\" value=\"([ /Mdy-]+)\">"); @@ -84,6 +84,7 @@ public final class GCConstants { public final static String ERROR_TB_DOES_NOT_EXIST = "does not exist in the system"; public final static String ERROR_TB_ELEMENT_EXCEPTION = "ElementNotFound Exception"; public final static String ERROR_TB_ARITHMETIC_OVERFLOW = "operation resulted in an overflow"; + public final static String ERROR_TB_NOT_ACTIVATED = "hasn't been activated"; /** * some parts of the webpage don't correctly encode the name, therefore this pattern must be checked with a * trackable name that needs HTML encoding @@ -121,9 +122,9 @@ public final class GCConstants { public final static Pattern PATTERN_SEARCH_DIFFICULTY_TERRAIN = Pattern.compile("<span class=\"small\">([0-5]([\\.,]5)?)/([0-5]([\\.,]5)?)</span><br />"); public final static Pattern PATTERN_SEARCH_CONTAINER = Pattern.compile("<img src=\"/images/icons/container/([^\\.]+)\\.gif\""); public final static Pattern PATTERN_SEARCH_GEOCODE = Pattern.compile("\\|\\W*(GC[0-9A-Z]+)[^\\|]*\\|"); - public final static Pattern PATTERN_SEARCH_ID = Pattern.compile("name=\"CID\"[^v]*value=\"([0-9]+)\""); - public final static Pattern PATTERN_SEARCH_FAVORITE = Pattern.compile("<span id=\"ctl00_ContentBody_dlResults_ctl[0-9]+_uxFavoritesValue\" title=\"[^\"]*\" class=\"favorite-rank\">([0-9]+)</span>"); - public final static Pattern PATTERN_SEARCH_TOTALCOUNT = Pattern.compile("<td class=\"PageBuilderWidget\"><span>Total Records[^<]*<b>(\\d+)<\\/b>"); + public final static Pattern PATTERN_SEARCH_ID = Pattern.compile("name=\"CID\"[^v]*value=\"(\\d+)\""); + public final static Pattern PATTERN_SEARCH_FAVORITE = Pattern.compile("favorite-rank\">([0-9,.]+)</span>"); + public final static Pattern PATTERN_SEARCH_TOTALCOUNT = Pattern.compile("<span>Total Records\\D*(\\d+)<"); public final static Pattern PATTERN_SEARCH_RECAPTCHA = Pattern.compile("<script[^>]*src=\"[^\"]*/recaptcha/api/challenge\\?k=([^\"]+)\"[^>]*>"); public final static Pattern PATTERN_SEARCH_RECAPTCHACHALLENGE = Pattern.compile("challenge : '([^']+)'"); @@ -152,17 +153,11 @@ public final class GCConstants { public final static Pattern PATTERN_MAINTENANCE = Pattern.compile("<span id=\"ctl00_ContentBody_LogBookPanel1_lbConfirm\"[^>]*>([^<]*<font[^>]*>)?([^<]+)(</font>[^<]*)?</span>", Pattern.CASE_INSENSITIVE); public final static Pattern PATTERN_OK1 = Pattern.compile("<h2[^>]*>[^<]*<span id=\"ctl00_ContentBody_lbHeading\"[^>]*>[^<]*</span>[^<]*</h2>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_OK2 = Pattern.compile("<div id=[\"|']ctl00_ContentBody_LogBookPanel1_ViewLogPanel[\"|']>", Pattern.CASE_INSENSITIVE); - public final static Pattern PATTERN_OK_IMAGEUPLOAD = Pattern.compile("<div id=[\"|']ctl00_ContentBody_ImageUploadControl1_uxUploadDonePanel[\"|']>", Pattern.CASE_INSENSITIVE); + public final static Pattern PATTERN_IMAGE_UPLOAD_URL = Pattern.compile("title=\"Click for Larger Image\"\\s*src=\"(.*?)\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_VIEWSTATEFIELDCOUNT = Pattern.compile("id=\"__VIEWSTATEFIELDCOUNT\"[^(value)]+value=\"(\\d+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_VIEWSTATES = Pattern.compile("id=\"__VIEWSTATE(\\d*)\"[^(value)]+value=\"([^\"]+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_USERTOKEN = Pattern.compile("userToken\\s*=\\s*'([^']+)'"); - /** - * Patterns for GC and TB codes - */ - public final static Pattern PATTERN_GC_CODE = Pattern.compile("GC[0-9A-Z]+", Pattern.CASE_INSENSITIVE); - public final static Pattern PATTERN_TB_CODE = Pattern.compile("TB[0-9A-Z]+", Pattern.CASE_INSENSITIVE); - /** Live Map since 14.02.2012 */ public final static Pattern PATTERN_USERSESSION = Pattern.compile("UserSession\\('([^']+)'"); public final static Pattern PATTERN_SESSIONTOKEN = Pattern.compile("sessionToken:'([^']+)'"); @@ -194,7 +189,7 @@ public final class GCConstants { */ public static long gccodeToGCId(final String gccode) { long base = GC_BASE31; - String geocodeWO = gccode.substring(2).toUpperCase(Locale.US); + final String geocodeWO = gccode.substring(2).toUpperCase(Locale.US); if ((geocodeWO.length() < 4) || (geocodeWO.length() == 4 && SEQUENCE_GCID.indexOf(geocodeWO.charAt(0)) < 16)) { base = GC_BASE16; diff --git a/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java b/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java new file mode 100644 index 0000000..2aa5c75 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java @@ -0,0 +1,132 @@ +package cgeo.geocaching.connector.gc; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.LogCacheActivity; +import cgeo.geocaching.R; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.TrackableLog; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.ImageResult; +import cgeo.geocaching.connector.LogResult; +import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.loaders.UrlLoader; +import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import android.app.Activity; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; + +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +public class GCLoggingManager implements ILoggingManager, LoaderManager.LoaderCallbacks<String> { + + private final LogCacheActivity activity; + private final Geocache cache; + + private String[] viewstates; + private List<TrackableLog> trackables; + private List<LogType> possibleLogTypes; + private boolean hasLoaderError = true; + + public GCLoggingManager(Activity activity, Geocache cache) { + this.activity = (LogCacheActivity) activity; + this.cache = cache; + } + + @Override + public Loader<String> onCreateLoader(int arg0, Bundle arg1) { + if (!Settings.isLogin()) { // allow offline logging + ActivityMixin.showToast(activity, activity.getResources().getString(R.string.err_login)); + return null; + } + return new UrlLoader(activity.getBaseContext(), "http://www.geocaching.com/seek/log.aspx", new Parameters("ID", cache.getCacheId())); + } + + @Override + public void onLoadFinished(Loader<String> arg0, String page) { + + if (page == null) { + hasLoaderError = true; + } else { + + viewstates = Login.getViewstates(page); + trackables = GCParser.parseTrackableLog(page); + possibleLogTypes = GCParser.parseTypes(page); + + hasLoaderError = possibleLogTypes.isEmpty(); + } + + activity.onLoadFinished(); + } + + @Override + public void onLoaderReset(Loader<String> arg0) { + // nothing to do + } + + @Override + public void init() { + activity.getSupportLoaderManager().initLoader(0, null, this); + } + + @Override + public LogResult postLog(Geocache cache, LogType logType, Calendar date, String log, String logPassword, List<TrackableLog> trackableLogs) { + + try { + final ImmutablePair<StatusCode, String> postResult = GCParser.postLog(cache.getGeocode(), cache.getCacheId(), viewstates, logType, + date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), + log, trackableLogs); + + return new LogResult(postResult.left, postResult.right); + } catch (Exception e) { + Log.e("GCLoggingManager.postLog", e); + } + + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } + + @Override + public ImageResult postLogImage(String logId, String imageCaption, String imageDescription, Uri imageUri) { + + if (StringUtils.isNotBlank(imageUri.getPath())) { + + ImmutablePair<StatusCode, String> imageResult = GCParser.uploadLogImage(logId, imageCaption, imageDescription, imageUri); + + return new ImageResult(imageResult.left, imageResult.right); + } + + return new ImageResult(StatusCode.LOGIMAGE_POST_ERROR, ""); + } + + @Override + public boolean hasLoaderError() { + return hasLoaderError; + } + + @Override + public List<TrackableLog> getTrackables() { + if (hasLoaderError) { + return Collections.emptyList(); + } + return trackables; + } + + @Override + public List<LogType> getPossibleLogTypes() { + if (hasLoaderError) { + return Collections.emptyList(); + } + return possibleLogTypes; + } + +} diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index 49f61ef..4bc55fe 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -2,7 +2,7 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.Geocache; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.Settings; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.CacheSize; @@ -92,7 +92,7 @@ public class GCMap { JSONObject ownerObj = dataObject.getJSONObject("owner"); cache.setOwnerDisplayName(ownerObj.getString("text")); - result.addCache(cache); + result.addAndPutInCache(cache); } } catch (JSONException e) { @@ -231,13 +231,13 @@ public class GCMap { exclude = true; } if (!exclude) { - searchResult.addCache(cache); + searchResult.addAndPutInCache(cache); } } Log.d("Retrieved " + searchResult.getCount() + " caches for tile " + tile.toString()); } catch (Exception e) { - Log.e("GCBase.parseMapJSON", e); + Log.e("GCMap.parseMapJSON", e); } return searchResult; @@ -283,7 +283,7 @@ public class GCMap { * @return */ private static SearchResult searchByViewport(final Viewport viewport, final String[] tokens, Strategy strategy) { - Log.d("GCBase.searchByViewport" + viewport.toString()); + Log.d("GCMap.searchByViewport" + viewport.toString()); final SearchResult searchResult = new SearchResult(); searchResult.setUrl(GCConstants.URL_LIVE_MAP + "?ll=" + viewport.getCenter().getLatitude() + "," + viewport.getCenter().getLongitude()); @@ -330,14 +330,14 @@ public class GCMap { String data = Tile.requestMapInfo(GCConstants.URL_MAP_INFO, params, GCConstants.URL_LIVE_MAP); if (StringUtils.isEmpty(data)) { - Log.w("GCBase.searchByViewport: No data from server for tile (" + tile.getX() + "/" + tile.getY() + ")"); + Log.w("GCMap.searchByViewport: No data from server for tile (" + tile.getX() + "/" + tile.getY() + ")"); } else { final SearchResult search = GCMap.parseMapJSON(data, tile, bitmap, strategy); if (search == null || CollectionUtils.isEmpty(search.getGeocodes())) { - Log.e("GCBase.searchByViewport: No cache parsed for viewport " + viewport); + Log.e("GCMap.searchByViewport: No cache parsed for viewport " + viewport); } else { - searchResult.addGeocodes(search.getGeocodes()); + searchResult.addSearchResult(search); } Tile.Cache.add(tile); } @@ -349,20 +349,21 @@ public class GCMap { } } + + // Check for vanished found caches + if (tiles.iterator().next().getZoomlevel() >= Tile.ZOOMLEVEL_MIN_PERSONALIZED) { + searchResult.addFilteredGeocodes(cgData.getCachedMissingFromSearch(searchResult, tiles, GCConnector.getInstance(), Tile.ZOOMLEVEL_MIN_PERSONALIZED - 1)); + } } - if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY)) { + if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY) && Settings.isPremiumMember()) { final Geopoint center = viewport.getCenter(); if ((lastSearchViewport == null) || !lastSearchViewport.contains(center)) { //FIXME We don't have a RecaptchaReceiver!? SearchResult search = GCParser.searchByCoords(center, Settings.getCacheType(), false, null); if (search != null && !search.isEmpty()) { final Set<String> geocodes = search.getGeocodes(); - if (Settings.isPremiumMember()) { - lastSearchViewport = cgData.getBounds(geocodes); - } else { - lastSearchViewport = new Viewport(center, center); - } + lastSearchViewport = cgData.getBounds(geocodes); searchResult.addGeocodes(geocodes); } } diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 6b456fd..9ecb51b 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -5,7 +5,7 @@ import cgeo.geocaching.Image; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.Settings; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.Trackable; import cgeo.geocaching.TrackableLog; import cgeo.geocaching.Waypoint; @@ -28,10 +28,10 @@ import cgeo.geocaching.loaders.RecaptchaReceiver; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.DirectionImage; -import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; @@ -79,14 +79,14 @@ public abstract class GCParser { // recaptcha String recaptchaChallenge = null; if (showCaptcha) { - String recaptchaJsParam = BaseUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null); + final String recaptchaJsParam = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null); if (recaptchaJsParam != null) { final Parameters params = new Parameters("k", recaptchaJsParam.trim()); final String recaptchaJs = Network.getResponseData(Network.getRequest("http://www.google.com/recaptcha/api/challenge", params)); if (StringUtils.isNotBlank(recaptchaJs)) { - recaptchaChallenge = BaseUtils.getMatch(recaptchaJs, GCConstants.PATTERN_SEARCH_RECAPTCHACHALLENGE, true, 1, null, true); + recaptchaChallenge = TextUtils.getMatch(recaptchaJs, GCConstants.PATTERN_SEARCH_RECAPTCHACHALLENGE, true, 1, null, true); } } if (thread != null && StringUtils.isNotBlank(recaptchaChallenge)) { @@ -109,7 +109,7 @@ public abstract class GCParser { page = page.substring(startPos); // cut on <table startPos = page.indexOf('>'); - int endPos = page.indexOf("ctl00_ContentBody_UnitTxt"); + final int endPos = page.indexOf("ctl00_ContentBody_UnitTxt"); if (startPos == -1 || endPos == -1) { Log.e("GCParser.parseSearch: ID \"ctl00_ContentBody_UnitTxt\" not found on page"); return null; @@ -120,9 +120,10 @@ public abstract class GCParser { final String[] rows = page.split("<tr class="); final int rows_count = rows.length; + int excludedCaches = 0; for (int z = 1; z < rows_count; z++) { final Geocache cache = new Geocache(); - String row = rows[z]; + final String row = rows[z]; // check for cache type presence if (!row.contains("images/wpttypes")) { @@ -150,33 +151,35 @@ public abstract class GCParser { } } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse GUID and/or Disabled Log.w("GCParser.parseSearch: Failed to parse GUID and/or Disabled data"); } if (Settings.isExcludeDisabledCaches() && (cache.isDisabled() || cache.isArchived())) { // skip disabled and archived caches + excludedCaches++; continue; } - cache.setGeocode(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_GEOCODE, true, 1, cache.getGeocode(), true)); + cache.setGeocode(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_GEOCODE, true, 1, cache.getGeocode(), true)); // cache type - cache.setType(CacheType.getByPattern(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_TYPE, true, 1, null, true))); + cache.setType(CacheType.getByPattern(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_TYPE, true, 1, null, true))); // cache direction - image if (Settings.getLoadDirImg()) { - final String direction = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 1, null, false); + final String direction = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 1, null, false); if (direction != null) { cache.setDirectionImg(direction); } } // cache distance - estimated distance for basic members - final String distance = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 2, null, false); + final String distance = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 2, null, false); if (distance != null) { - cache.setDistance(DistanceParser.parseDistance(distance, Settings.isUseMetricUnits())); + cache.setDistance(DistanceParser.parseDistance(distance, + !Settings.isUseImperialUnits())); } // difficulty/terrain @@ -193,7 +196,7 @@ public abstract class GCParser { } // size - final String container = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_CONTAINER, false, 1, null, false); + final String container = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_CONTAINER, false, 1, null, false); cache.setSize(CacheSize.getById(container)); // cache inventory @@ -203,7 +206,7 @@ public abstract class GCParser { if (matcherTbs.groupCount() > 0) { try { cache.setInventoryItems(Integer.parseInt(matcherTbs.group(1))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing trackables count", e); } inventoryPre = matcherTbs.group(2); @@ -229,7 +232,7 @@ public abstract class GCParser { cache.setFound(row.contains("/images/icons/16/found.png")); // id - String result = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_ID, null); + String result = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_ID, null); if (null != result) { cache.setCacheId(result); cids.add(cache.getCacheId()); @@ -237,24 +240,24 @@ public abstract class GCParser { // favorite count try { - result = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_FAVORITE, false, 1, null, true); + result = getNumberString(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_FAVORITE, false, 1, null, true)); if (null != result) { cache.setFavoritePoints(Integer.parseInt(result)); } - } catch (NumberFormatException e) { - Log.w("GCParser.parseSearch: Failed to parse favourite count"); + } catch (final NumberFormatException e) { + Log.w("GCParser.parseSearch: Failed to parse favorite count"); } - searchResult.addCache(cache); + searchResult.addAndPutInCache(cache); } // total caches found try { - String result = BaseUtils.getMatch(page, GCConstants.PATTERN_SEARCH_TOTALCOUNT, false, 1, null, true); + final String result = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_TOTALCOUNT, false, 1, null, true); if (null != result) { - searchResult.setTotal(Integer.parseInt(result)); + searchResult.setTotal(Integer.parseInt(result) - excludedCaches); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.w("GCParser.parseSearch: Failed to parse cache count"); } @@ -284,7 +287,7 @@ public abstract class GCParser { params.put("__VIEWSTATEFIELDCOUNT", String.valueOf(searchResult.viewstates.length)); } } - for (String cid : cids) { + for (final String cid : cids) { params.put("CID", cid); } @@ -308,7 +311,7 @@ public abstract class GCParser { LocParser.parseLoc(searchResult, coordinates); - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.parseSearch.CIDs", e); } } @@ -316,7 +319,7 @@ public abstract class GCParser { // get direction images if (Settings.getLoadDirImg()) { final Set<Geocache> caches = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); - for (Geocache cache : caches) { + for (final Geocache cache : caches) { if (cache.getCoords() == null && StringUtils.isNotEmpty(cache.getDirectionImg())) { DirectionImage.getDrawable(cache.getDirectionImg()); } @@ -327,15 +330,17 @@ public abstract class GCParser { } private static Float parseStars(final String value) { - float floatValue = Float.parseFloat(StringUtils.replaceChars(value, ',', '.')); + final float floatValue = Float.parseFloat(StringUtils.replaceChars(value, ',', '.')); return floatValue >= 0.5 && floatValue <= 5.0 ? floatValue : null; } static SearchResult parseCache(final String page, final CancellableHandler handler) { final SearchResult searchResult = parseCacheFromText(page, handler); + // attention: parseCacheFromText already stores implicitely through searchResult.addCache if (searchResult != null && !searchResult.getGeocodes().isEmpty()) { final Geocache cache = searchResult.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); getExtraOnlineInfo(cache, page, handler); + // too late: it is already stored through parseCacheFromText cache.setDetailedUpdatedNow(); if (CancellableHandler.isCancelled(handler)) { return null; @@ -352,61 +357,70 @@ public abstract class GCParser { return searchResult; } - static SearchResult parseCacheFromText(final String page, final CancellableHandler handler) { + static SearchResult parseCacheFromText(final String pageIn, final CancellableHandler handler) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_details); - if (StringUtils.isBlank(page)) { + if (StringUtils.isBlank(pageIn)) { Log.e("GCParser.parseCache: No page given"); return null; } final SearchResult searchResult = new SearchResult(); - if (page.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || page.contains(GCConstants.STRING_UNPUBLISHED_OWNER) || page.contains(GCConstants.STRING_UNPUBLISHED_FROM_SEARCH)) { + if (pageIn.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || pageIn.contains(GCConstants.STRING_UNPUBLISHED_OWNER) || pageIn.contains(GCConstants.STRING_UNPUBLISHED_FROM_SEARCH)) { searchResult.setError(StatusCode.UNPUBLISHED_CACHE); return searchResult; } - if (page.contains(GCConstants.STRING_PREMIUMONLY_1) || page.contains(GCConstants.STRING_PREMIUMONLY_2)) { + if (pageIn.contains(GCConstants.STRING_PREMIUMONLY_1) || pageIn.contains(GCConstants.STRING_PREMIUMONLY_2)) { searchResult.setError(StatusCode.PREMIUM_ONLY); return searchResult; } - final String cacheName = Html.fromHtml(BaseUtils.getMatch(page, GCConstants.PATTERN_NAME, true, "")).toString(); + final String cacheName = Html.fromHtml(TextUtils.getMatch(pageIn, GCConstants.PATTERN_NAME, true, "")).toString(); if (GCConstants.STRING_UNKNOWN_ERROR.equalsIgnoreCase(cacheName)) { searchResult.setError(StatusCode.UNKNOWN_ERROR); return searchResult; } + // first handle the content with line breaks, then trim everything for easier matching and reduced memory consumption in parsed fields + String personalNoteWithLineBreaks = ""; + MatcherWrapper matcher = new MatcherWrapper(GCConstants.PATTERN_PERSONALNOTE, pageIn); + if (matcher.find()) { + personalNoteWithLineBreaks = matcher.group(1).trim(); + } + + final String page = TextUtils.replaceWhitespace(pageIn); + final Geocache cache = new Geocache(); cache.setDisabled(page.contains(GCConstants.STRING_DISABLED)); cache.setArchived(page.contains(GCConstants.STRING_ARCHIVED)); - cache.setPremiumMembersOnly(BaseUtils.matches(page, GCConstants.PATTERN_PREMIUMMEMBERS)); + cache.setPremiumMembersOnly(TextUtils.matches(page, GCConstants.PATTERN_PREMIUMMEMBERS)); - cache.setFavorite(BaseUtils.matches(page, GCConstants.PATTERN_FAVORITE)); + cache.setFavorite(TextUtils.matches(page, GCConstants.PATTERN_FAVORITE)); // cache geocode - cache.setGeocode(BaseUtils.getMatch(page, GCConstants.PATTERN_GEOCODE, true, cache.getGeocode())); + cache.setGeocode(TextUtils.getMatch(page, GCConstants.PATTERN_GEOCODE, true, cache.getGeocode())); // cache id - cache.setCacheId(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHEID, true, cache.getCacheId())); + cache.setCacheId(TextUtils.getMatch(page, GCConstants.PATTERN_CACHEID, true, cache.getCacheId())); // cache guid - cache.setGuid(BaseUtils.getMatch(page, GCConstants.PATTERN_GUID, true, cache.getGuid())); + cache.setGuid(TextUtils.getMatch(page, GCConstants.PATTERN_GUID, true, cache.getGuid())); // name cache.setName(cacheName); // owner real name - cache.setOwnerUserId(Network.decode(BaseUtils.getMatch(page, GCConstants.PATTERN_OWNER_USERID, true, cache.getOwnerUserId()))); + cache.setOwnerUserId(Network.decode(TextUtils.getMatch(page, GCConstants.PATTERN_OWNER_USERID, true, cache.getOwnerUserId()))); cache.setUserModifiedCoords(false); String tableInside = page; - int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS); + final int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS); if (pos == -1) { Log.e("GCParser.parseCache: ID \"cacheDetails\" not found on page"); return null; @@ -416,96 +430,96 @@ public abstract class GCParser { if (StringUtils.isNotBlank(tableInside)) { // cache terrain - String result = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_TERRAIN, true, null); + String result = TextUtils.getMatch(tableInside, GCConstants.PATTERN_TERRAIN, true, null); if (result != null) { try { cache.setTerrain(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing terrain value", e); } } // cache difficulty - result = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_DIFFICULTY, true, null); + result = TextUtils.getMatch(tableInside, GCConstants.PATTERN_DIFFICULTY, true, null); if (result != null) { try { cache.setDifficulty(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing difficulty value", e); } } // owner - cache.setOwnerDisplayName(StringEscapeUtils.unescapeHtml4(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_OWNER_DISPLAYNAME, true, cache.getOwnerDisplayName()))); + cache.setOwnerDisplayName(StringEscapeUtils.unescapeHtml4(TextUtils.getMatch(tableInside, GCConstants.PATTERN_OWNER_DISPLAYNAME, true, cache.getOwnerDisplayName()))); // hidden try { - String hiddenString = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDEN, true, null); + String hiddenString = TextUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDEN, true, null); if (StringUtils.isNotBlank(hiddenString)) { cache.setHidden(Login.parseGcCustomDate(hiddenString)); } if (cache.getHiddenDate() == null) { // event date - hiddenString = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDENEVENT, true, null); + hiddenString = TextUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDENEVENT, true, null); if (StringUtils.isNotBlank(hiddenString)) { cache.setHidden(Login.parseGcCustomDate(hiddenString)); } } - } catch (ParseException e) { + } catch (final ParseException e) { // failed to parse cache hidden date Log.w("GCParser.parseCache: Failed to parse cache hidden (event) date"); } - // favourite + // favorite try { - cache.setFavoritePoints(Integer.parseInt(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_FAVORITECOUNT, true, "0"))); - } catch (NumberFormatException e) { - Log.e("Error parsing favourite count", e); + cache.setFavoritePoints(Integer.parseInt(TextUtils.getMatch(tableInside, GCConstants.PATTERN_FAVORITECOUNT, true, "0"))); + } catch (final NumberFormatException e) { + Log.e("Error parsing favorite count", e); } // cache size - cache.setSize(CacheSize.getById(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_SIZE, true, CacheSize.NOT_CHOSEN.id))); + cache.setSize(CacheSize.getById(TextUtils.getMatch(tableInside, GCConstants.PATTERN_SIZE, true, CacheSize.NOT_CHOSEN.id))); } // cache found - cache.setFound(BaseUtils.matches(page, GCConstants.PATTERN_FOUND) || BaseUtils.matches(page, GCConstants.PATTERN_FOUND_ALTERNATIVE)); + cache.setFound(TextUtils.matches(page, GCConstants.PATTERN_FOUND) || TextUtils.matches(page, GCConstants.PATTERN_FOUND_ALTERNATIVE)); // cache found date try { - final String foundDateString = BaseUtils.getMatch(page, GCConstants.PATTERN_FOUND_DATE, true, null); + final String foundDateString = TextUtils.getMatch(page, GCConstants.PATTERN_FOUND_DATE, true, null); if (StringUtils.isNotBlank(foundDateString)) { cache.setVisitedDate(Login.parseGcCustomDate(foundDateString).getTime()); } - } catch (ParseException e) { + } catch (final ParseException e) { // failed to parse cache found date Log.w("GCParser.parseCache: Failed to parse cache found date"); } // cache type - cache.setType(CacheType.getByPattern(BaseUtils.getMatch(page, GCConstants.PATTERN_TYPE, true, cache.getType().id))); + cache.setType(CacheType.getByPattern(TextUtils.getMatch(page, GCConstants.PATTERN_TYPE, true, cache.getType().id))); // on watchlist - cache.setOnWatchlist(BaseUtils.matches(page, GCConstants.PATTERN_WATCHLIST)); + cache.setOnWatchlist(TextUtils.matches(page, GCConstants.PATTERN_WATCHLIST)); // latitude and longitude. Can only be retrieved if user is logged in - String latlon = BaseUtils.getMatch(page, GCConstants.PATTERN_LATLON, true, ""); + String latlon = TextUtils.getMatch(page, GCConstants.PATTERN_LATLON, true, ""); if (StringUtils.isNotEmpty(latlon)) { try { cache.setCoords(new Geopoint(latlon)); cache.setReliableLatLon(true); - } catch (Geopoint.GeopointException e) { + } catch (final Geopoint.GeopointException e) { Log.w("GCParser.parseCache: Failed to parse cache coordinates", e); } } // cache location - cache.setLocation(BaseUtils.getMatch(page, GCConstants.PATTERN_LOCATION, true, "")); + cache.setLocation(TextUtils.getMatch(page, GCConstants.PATTERN_LOCATION, true, "")); // cache hint - String result = BaseUtils.getMatch(page, GCConstants.PATTERN_HINT, false, null); + final String result = TextUtils.getMatch(page, GCConstants.PATTERN_HINT, false, null); if (result != null) { // replace linebreak and paragraph tags - String hint = GCConstants.PATTERN_LINEBREAK.matcher(result).replaceAll("\n"); + final String hint = GCConstants.PATTERN_LINEBREAK.matcher(result).replaceAll("\n"); if (hint != null) { cache.setHint(StringUtils.replace(hint, "</p>", "").trim()); } @@ -514,17 +528,17 @@ public abstract class GCParser { cache.checkFields(); // cache personal note - cache.setPersonalNote(BaseUtils.getMatch(page, GCConstants.PATTERN_PERSONALNOTE, true, cache.getPersonalNote())); + cache.setPersonalNote(personalNoteWithLineBreaks); // cache short description - cache.setShortDescription(BaseUtils.getMatch(page, GCConstants.PATTERN_SHORTDESC, true, "")); + cache.setShortDescription(TextUtils.getMatch(page, GCConstants.PATTERN_SHORTDESC, true, "")); // cache description - cache.setDescription(BaseUtils.getMatch(page, GCConstants.PATTERN_DESC, true, "")); + cache.setDescription(TextUtils.getMatch(page, GCConstants.PATTERN_DESC, true, "")); // cache attributes try { - final String attributesPre = BaseUtils.getMatch(page, GCConstants.PATTERN_ATTRIBUTES, true, null); + final String attributesPre = TextUtils.getMatch(page, GCConstants.PATTERN_ATTRIBUTES, true, null); if (null != attributesPre) { final MatcherWrapper matcherAttributesInside = new MatcherWrapper(GCConstants.PATTERN_ATTRIBUTESINSIDE, attributesPre); @@ -537,8 +551,8 @@ public abstract class GCParser { // if the image name can be recognized, use the image name as attribute final String imageName = matcherAttributesInside.group(1).trim(); if (StringUtils.isNotEmpty(imageName)) { - int start = imageName.lastIndexOf('/'); - int end = imageName.lastIndexOf('.'); + final int start = imageName.lastIndexOf('/'); + final int end = imageName.lastIndexOf('.'); if (start >= 0 && end >= 0) { attribute = imageName.substring(start + 1, end).replace('-', '_').toLowerCase(Locale.US); } @@ -548,7 +562,7 @@ public abstract class GCParser { } cache.setAttributes(attributes); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse cache attributes Log.w("GCParser.parseCache: Failed to parse cache attributes"); } @@ -565,7 +579,7 @@ public abstract class GCParser { while (matcherSpoilersInside.find()) { // the original spoiler URL (include .../display/... contains a low-resolution image // if we shorten the URL we get the original-resolution image - String url = matcherSpoilersInside.group(1).replace("/display", ""); + final String url = matcherSpoilersInside.group(1).replace("/display", ""); String title = null; if (matcherSpoilersInside.group(3) != null) { @@ -577,7 +591,7 @@ public abstract class GCParser { } cache.addSpoiler(new Image(url, title, description)); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse cache spoilers Log.w("GCParser.parseCache: Failed to parse cache spoilers"); } @@ -611,20 +625,20 @@ public abstract class GCParser { } } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse cache inventory Log.w("GCParser.parseCache: Failed to parse cache inventory (2)"); } // cache logs counts try { - final String countlogs = BaseUtils.getMatch(page, GCConstants.PATTERN_COUNTLOGS, true, null); + final String countlogs = TextUtils.getMatch(page, GCConstants.PATTERN_COUNTLOGS, true, null); if (null != countlogs) { final MatcherWrapper matcherLog = new MatcherWrapper(GCConstants.PATTERN_COUNTLOG, countlogs); while (matcherLog.find()) { - String typeStr = matcherLog.group(1); - String countStr = matcherLog.group(2).replaceAll("[.,]", ""); + final String typeStr = matcherLog.group(1); + final String countStr = getNumberString(matcherLog.group(2)); if (StringUtils.isNotBlank(typeStr) && LogType.UNKNOWN != LogType.getByIconName(typeStr) @@ -633,7 +647,7 @@ public abstract class GCParser { } } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse logs Log.w("GCParser.parseCache: Failed to parse cache log count"); } @@ -643,7 +657,7 @@ public abstract class GCParser { // add waypoint for original coordinates in case of user-modified listing-coordinates try { - final String originalCoords = BaseUtils.getMatch(page, GCConstants.PATTERN_LATLON_ORIG, false, null); + final String originalCoords = TextUtils.getMatch(page, GCConstants.PATTERN_LATLON_ORIG, false, null); if (null != originalCoords) { final Waypoint waypoint = new Waypoint(cgeoapplication.getInstance().getString(R.string.cache_coordinates_original), WaypointType.ORIGINAL, false); @@ -651,7 +665,7 @@ public abstract class GCParser { cache.addOrChangeWaypoint(waypoint, false); cache.setUserModifiedCoords(true); } - } catch (Geopoint.GeopointException e) { + } catch (final Geopoint.GeopointException e) { } int wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">"); @@ -685,21 +699,21 @@ public abstract class GCParser { // waypoint name // res is null during the unit tests - final String name = BaseUtils.getMatch(wp[6], GCConstants.PATTERN_WPNAME, true, 1, cgeoapplication.getInstance().getString(R.string.waypoint), true); + final String name = TextUtils.getMatch(wp[6], GCConstants.PATTERN_WPNAME, true, 1, cgeoapplication.getInstance().getString(R.string.waypoint), true); // waypoint type - final String resulttype = BaseUtils.getMatch(wp[3], GCConstants.PATTERN_WPTYPE, null); + final String resulttype = TextUtils.getMatch(wp[3], GCConstants.PATTERN_WPTYPE, null); final Waypoint waypoint = new Waypoint(name, WaypointType.findById(resulttype), false); // waypoint prefix - waypoint.setPrefix(BaseUtils.getMatch(wp[4], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, true, 2, waypoint.getPrefix(), false)); + waypoint.setPrefix(TextUtils.getMatch(wp[4], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, true, 2, waypoint.getPrefix(), false)); // waypoint lookup - waypoint.setLookup(BaseUtils.getMatch(wp[5], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, true, 2, waypoint.getLookup(), false)); + waypoint.setLookup(TextUtils.getMatch(wp[5], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, true, 2, waypoint.getLookup(), false)); // waypoint latitude and logitude - latlon = Html.fromHtml(BaseUtils.getMatch(wp[7], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, false, 2, "", false)).toString().trim(); + latlon = Html.fromHtml(TextUtils.getMatch(wp[7], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, false, 2, "", false)).toString().trim(); if (!StringUtils.startsWith(latlon, "???")) { waypoint.setLatlon(latlon); waypoint.setCoords(new Geopoint(latlon)); @@ -711,7 +725,7 @@ public abstract class GCParser { } // waypoint note - waypoint.setNote(BaseUtils.getMatch(wp[3], GCConstants.PATTERN_WPNOTE, waypoint.getNote())); + waypoint.setNote(TextUtils.getMatch(wp[3], GCConstants.PATTERN_WPNOTE, waypoint.getNote())); cache.addOrChangeWaypoint(waypoint, false); } @@ -729,10 +743,18 @@ public abstract class GCParser { return searchResult; } - searchResult.addCache(cache); + cache.setDetailedUpdatedNow(); + searchResult.addAndPutInCache(cache); return searchResult; } + private static String getNumberString(final String numberWithPunctuation) { + if (numberWithPunctuation == null) { + return null; + } + return numberWithPunctuation.replaceAll("[.,]", ""); + } + public static SearchResult searchByNextPage(final SearchResult search, boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { if (search == null) { return search; @@ -782,7 +804,7 @@ public abstract class GCParser { // save to application search.setError(searchResult.getError()); search.setViewstates(searchResult.viewstates); - for (String geocode : searchResult.getGeocodes()) { + for (final String geocode : searchResult.getGeocodes()) { search.addGeocode(geocode); } return search; @@ -859,7 +881,7 @@ public abstract class GCParser { } private static boolean isSearchForMyCaches(final String userName) { - if (userName.equalsIgnoreCase(Settings.getLogin().left)) { + if (userName.equalsIgnoreCase(Settings.getGcLogin().left)) { Log.i("Overriding users choice because of self search, downloading all caches."); return true; } @@ -893,7 +915,7 @@ public abstract class GCParser { return null; } try { - JSONObject response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address)); + final JSONObject response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address)); if (response == null) { return null; } @@ -903,12 +925,12 @@ public abstract class GCParser { if (!response.has("data")) { return null; } - JSONObject data = response.getJSONObject("data"); + final JSONObject data = response.getJSONObject("data"); if (data == null) { return null; } return searchByCoords(new Geopoint(data.getDouble("lat"), data.getDouble("lng")), cacheType, showCaptcha, recaptchaReceiver); - } catch (JSONException e) { + } catch (final JSONException e) { Log.w("GCParser.searchByAddress", e); } @@ -1005,7 +1027,7 @@ public abstract class GCParser { final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/log.aspx").encodedQuery("ID=" + cacheid).build().toString(); String page = Login.postRequestLogged(uri, params); if (!Login.getLoginStatus(page)) { - Log.e("GCParser.postLogTrackable: Can not log in geocaching"); + Log.e("GCParser.postLog: Cannot log in geocaching"); return new ImmutablePair<StatusCode, String>(StatusCode.NOT_LOGGED_IN, ""); } @@ -1033,7 +1055,7 @@ public abstract class GCParser { if (trackables != null && !trackables.isEmpty()) { // we have some trackables to proceed final StringBuilder hdnSelected = new StringBuilder(); - for (TrackableLog tb : trackables) { + for (final TrackableLog tb : trackables) { final String action = Integer.toString(tb.id) + tb.action.action; final StringBuilder paramText = new StringBuilder("ctl00$ContentBody$LogBookPanel1$uxTrackables$repTravelBugs$ctl"); @@ -1054,7 +1076,7 @@ public abstract class GCParser { page = Network.getResponseData(Network.postRequest(uri, params)); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.postLog.confim", e); } @@ -1074,11 +1096,11 @@ public abstract class GCParser { Login.setActualCachesFound(Login.getActualCachesFound() + 1); } - final String logID = BaseUtils.getMatch(page, GCConstants.PATTERN_LOG_IMAGE_UPLOAD, ""); + final String logID = TextUtils.getMatch(page, GCConstants.PATTERN_LOG_IMAGE_UPLOAD, ""); return new ImmutablePair<StatusCode, String>(StatusCode.NO_ERROR, logID); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.postLog.check", e); } @@ -1099,20 +1121,13 @@ public abstract class GCParser { * the URI for the image to be uploaded * @return status code to indicate success or failure */ - public static StatusCode uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) { - final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/upload.aspx").encodedQuery("LID=" + logId).build().toString(); + public static ImmutablePair<StatusCode, String> uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) { + final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/upload.aspx").build().toString(); - String page = Network.getResponseData(Network.getRequest(uri)); - - if (!Login.getLoginStatus(page)) { - // Login.isActualLoginStatus() was wrong, we are not logged in - final StatusCode loginState = Login.login(); - if (loginState == StatusCode.NO_ERROR) { - page = Network.getResponseData(Network.getRequest(uri)); - } else { - Log.e("Image upload: No login (error: " + loginState + ')'); - return StatusCode.NOT_LOGGED_IN; - } + final String page = Login.getRequestLogged(uri, new Parameters("LID=", logId)); + if (StringUtils.isBlank(page)) { + Log.e("GCParser.uploadLogImage: No data from server"); + return new ImmutablePair<StatusCode, String>(StatusCode.UNKNOWN_ERROR, null); } final String[] viewstates = Login.getViewstates(page); @@ -1128,18 +1143,23 @@ public abstract class GCParser { final File image = new File(imageUri.getPath()); final String response = Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$ImageUploadControl1$uxFileUpload", "image/jpeg", image)); - MatcherWrapper matcherOK = new MatcherWrapper(GCConstants.PATTERN_OK_IMAGEUPLOAD, response); + final MatcherWrapper matcherUrl = new MatcherWrapper(GCConstants.PATTERN_IMAGE_UPLOAD_URL, response); - if (matcherOK.find()) { + if (matcherUrl.find()) { Log.i("Logimage successfully uploaded."); - - return StatusCode.NO_ERROR; + final String uploadedImageUrl = matcherUrl.group(1); + return ImmutablePair.of(StatusCode.NO_ERROR, uploadedImageUrl); } Log.e("GCParser.uploadLogIMage: Failed to upload image because of unknown error"); - return StatusCode.LOGIMAGE_POST_ERROR; + return ImmutablePair.of(StatusCode.LOGIMAGE_POST_ERROR, null); } + /** + * Post a log to GC.com. + * + * @return status code of the upload and ID of the log + */ public static StatusCode postLogTrackable(final String tbid, final String trackingCode, final String[] viewstates, final LogType logType, final int year, final int month, final int day, final String log) { if (Login.isEmpty(viewstates)) { @@ -1187,7 +1207,7 @@ public abstract class GCParser { final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/track/log.aspx").encodedQuery("wid=" + tbid).build().toString(); final String page = Login.postRequestLogged(uri, params); if (!Login.getLoginStatus(page)) { - Log.e("GCParser.postLogTrackable: Can not log in geocaching"); + Log.e("GCParser.postLogTrackable: Cannot log in geocaching"); return StatusCode.NOT_LOGGED_IN; } @@ -1198,7 +1218,7 @@ public abstract class GCParser { Log.i("Log successfully posted to trackable #" + trackingCode); return StatusCode.NO_ERROR; } - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.postLogTrackable.check", e); } @@ -1215,14 +1235,14 @@ public abstract class GCParser { */ static boolean addToWatchlist(final Geocache cache) { final String uri = "http://www.geocaching.com/my/watchlist.aspx?w=" + cache.getCacheId(); - String page = Login.postRequestLogged(uri, null); + final String page = Login.postRequestLogged(uri, null); if (StringUtils.isBlank(page)) { Log.e("GCParser.addToWatchlist: No data from server"); return false; // error } - boolean guidOnPage = cache.isGuidContainedInPage(page); + final boolean guidOnPage = cache.isGuidContainedInPage(page); if (guidOnPage) { Log.i("GCParser.addToWatchlist: cache is on watchlist"); cache.setOnWatchlist(true); @@ -1256,7 +1276,7 @@ public abstract class GCParser { Login.transferViewstates(page, params); page = Network.getResponseData(Network.postRequest(uri, params)); - boolean guidOnPage = cache.isGuidContainedInPage(page); + final boolean guidOnPage = cache.isGuidContainedInPage(page); if (!guidOnPage) { Log.i("GCParser.removeFromWatchlist: cache removed from watchlist"); cache.setOnWatchlist(false); @@ -1294,14 +1314,14 @@ 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 = BaseUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); if (StringUtils.isEmpty(userToken)) { return false; } final String uri = "http://www.geocaching.com/datastore/favorites.svc/update?u=" + userToken + "&f=" + Boolean.toString(add); - HttpResponse response = Network.postRequest(uri, null); + final HttpResponse response = Network.postRequest(uri, null); if (response != null && response.getStatusLine().getStatusCode() == 200) { Log.i("GCParser.changeFavorite: cache added/removed to/from favorites"); @@ -1330,7 +1350,7 @@ public abstract class GCParser { * Parse a trackable HTML description into a Trackable object * * @param page - * the HTML page to parse, already processed through {@link BaseUtils#replaceWhitespace} + * the HTML page to parse, already processed through {@link TextUtils#replaceWhitespace} * @return the parsed trackable, or null if none could be parsed */ static Trackable parseTrackable(final String page, final String possibleTrackingcode) { @@ -1346,20 +1366,20 @@ public abstract class GCParser { final Trackable trackable = new Trackable(); // trackable geocode - trackable.setGeocode(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, trackable.getGeocode())); + trackable.setGeocode(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, StringUtils.upperCase(possibleTrackingcode))); // trackable id - trackable.setGuid(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GUID, true, trackable.getGuid())); + trackable.setGuid(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GUID, true, trackable.getGuid())); // trackable icon - trackable.setIconUrl(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_ICON, true, trackable.getIconUrl())); + trackable.setIconUrl(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_ICON, true, trackable.getIconUrl())); // trackable name - trackable.setName(Html.fromHtml(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_NAME, true, "")).toString()); + trackable.setName(Html.fromHtml(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_NAME, true, "")).toString()); // trackable type if (StringUtils.isNotBlank(trackable.getName())) { - trackable.setType(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_TYPE, true, trackable.getType())); + trackable.setType(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_TYPE, true, trackable.getType())); } // trackable owner name @@ -1369,13 +1389,13 @@ public abstract class GCParser { trackable.setOwnerGuid(matcherOwner.group(1)); trackable.setOwner(matcherOwner.group(2).trim()); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse trackable owner name Log.w("GCParser.parseTrackable: Failed to parse trackable owner name"); } // trackable origin - trackable.setOrigin(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_ORIGIN, true, trackable.getOrigin())); + trackable.setOrigin(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_ORIGIN, true, trackable.getOrigin())); // trackable spotted try { @@ -1393,39 +1413,44 @@ public abstract class GCParser { trackable.setSpottedType(Trackable.SPOTTED_USER); } - if (BaseUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDUNKNOWN)) { + if (TextUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDUNKNOWN)) { trackable.setSpottedType(Trackable.SPOTTED_UNKNOWN); } - if (BaseUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDOWNER)) { + if (TextUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDOWNER)) { trackable.setSpottedType(Trackable.SPOTTED_OWNER); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse trackable last known place Log.w("GCParser.parseTrackable: Failed to parse trackable last known place"); } // released date - can be missing on the page try { - String releaseString = BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_RELEASES, false, null); + final String releaseString = TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_RELEASES, false, null); if (releaseString != null) { trackable.setReleased(dateTbIn1.parse(releaseString)); if (trackable.getReleased() == null) { trackable.setReleased(dateTbIn2.parse(releaseString)); } } - } catch (ParseException e1) { + } catch (final ParseException e1) { trackable.setReleased(null); } // trackable distance - final String distance = BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_DISTANCE, false, null); + final String distance = TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_DISTANCE, false, null); if (null != distance) { - trackable.setDistance(DistanceParser.parseDistance(distance, Settings.isUseMetricUnits())); + try { + trackable.setDistance(DistanceParser.parseDistance(distance, + !Settings.isUseImperialUnits())); + } catch (final NumberFormatException e) { + Log.e("GCParser.parseTrackable: Failed to parse distance", e); + } } // trackable goal - trackable.setGoal(convertLinks(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal()))); + trackable.setGoal(convertLinks(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal()))); // trackable details & image try { @@ -1441,10 +1466,13 @@ public abstract class GCParser { trackable.setDetails(convertLinks(details)); } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse trackable details & image Log.w("GCParser.parseTrackable: Failed to parse trackable details & image"); } + if (StringUtils.isEmpty(trackable.getDetails()) && page.contains(GCConstants.ERROR_TB_NOT_ACTIVATED)) { + trackable.setDetails(cgeoapplication.getInstance().getString(R.string.trackable_not_activated)); + } // trackable logs try { @@ -1462,7 +1490,7 @@ public abstract class GCParser { long date = 0; try { date = Login.parseGcCustomDate(matcherLogs.group(2)).getTime(); - } catch (ParseException e) { + } catch (final ParseException e) { } final LogEntry logDone = new LogEntry( @@ -1490,7 +1518,7 @@ public abstract class GCParser { trackable.getLogs().add(logDone); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse logs Log.w("GCParser.parseCache: Failed to parse cache logs", e); } @@ -1560,10 +1588,10 @@ public abstract class GCParser { } } else { // extract embedded JSON data from page - rawResponse = BaseUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, ""); + rawResponse = TextUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, ""); } - List<LogEntry> logs = new ArrayList<LogEntry>(); + final List<LogEntry> logs = new ArrayList<LogEntry>(); try { final JSONObject resp = new JSONObject(rawResponse); @@ -1584,7 +1612,7 @@ public abstract class GCParser { long date = 0; try { date = Login.parseGcCustomDate(entry.getString("Visited")).getTime(); - } catch (ParseException e) { + } catch (final ParseException e) { Log.e("GCParser.loadLogsFromDetails: failed to parse log date."); } @@ -1612,7 +1640,7 @@ public abstract class GCParser { logs.add(logDone); } - } catch (JSONException e) { + } catch (final JSONException e) { // failed to parse logs Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e); } @@ -1628,30 +1656,26 @@ public abstract class GCParser { final List<LogType> types = new ArrayList<LogType>(); final MatcherWrapper typeBoxMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPEBOX, page); - String typesText = null; - if (typeBoxMatcher.find()) { - if (typeBoxMatcher.groupCount() > 0) { - typesText = typeBoxMatcher.group(1); - } - } - - if (typesText != null) { - + if (typeBoxMatcher.find() && typeBoxMatcher.groupCount() > 0) { + final String typesText = typeBoxMatcher.group(1); final MatcherWrapper typeMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPE2, typesText); while (typeMatcher.find()) { if (typeMatcher.groupCount() > 1) { try { - int type = Integer.parseInt(typeMatcher.group(2)); + final int type = Integer.parseInt(typeMatcher.group(2)); if (type > 0) { types.add(LogType.getById(type)); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing log types", e); } } } } + // we don't support this log type + types.remove(LogType.UPDATE_COORDINATES); + return types; } @@ -1690,7 +1714,7 @@ public abstract class GCParser { Log.i("Trackable in inventory (#" + entry.ctl + "/" + entry.id + "): " + entry.trackCode + " - " + entry.name); trackableLogs.add(entry); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("GCParser.parseTrackableLog", e); } } @@ -1719,10 +1743,10 @@ public abstract class GCParser { //cache.setLogs(loadLogsFromDetails(page, cache, false)); if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); - List<LogEntry> allLogs = cache.getLogs(); - List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); + final List<LogEntry> allLogs = cache.getLogs(); + final List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); if (friendLogs != null) { - for (LogEntry log : friendLogs) { + for (final LogEntry log : friendLogs) { if (allLogs.contains(log)) { allLogs.get(allLogs.indexOf(log)).friend = true; } else { @@ -1732,16 +1756,6 @@ public abstract class GCParser { } } - if (Settings.isElevationWanted()) { - if (CancellableHandler.isCancelled(handler)) { - return; - } - CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_elevation); - if (cache.getCoords() != null) { - cache.setElevation(cache.getCoords().getElevation()); - } - } - if (Settings.isRatingWanted()) { if (CancellableHandler.isCancelled(handler)) { return; @@ -1766,7 +1780,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 = BaseUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); if (StringUtils.isEmpty(userToken)) { return false; } @@ -1785,7 +1799,7 @@ public abstract class GCParser { final String uriSuffix = wpt != null ? "SetUserCoordinate" : "ResetUserCoordinate"; final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; - HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); + final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); if (response != null && response.getStatusLine().getStatusCode() == 200) { @@ -1793,11 +1807,42 @@ public abstract class GCParser { return true; } - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("Unknown exception with json wrap code", e); } Log.e("GCParser.deleteModifiedCoordinates - cannot delete modified coords"); return false; } + public static boolean uploadPersonalNote(Geocache cache) { + final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); + final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + if (StringUtils.isEmpty(userToken)) { + return false; + } + + try { + final JSONObject jo = new JSONObject() + .put("dto", (new JSONObject() + .put("et", cache.getPersonalNote()) + .put("ut", userToken))); + + final String uriSuffix = "SetUserCacheNote"; + + final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; + final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); + Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); + + if (response != null && response.getStatusLine().getStatusCode() == 200) { + Log.i("GCParser.uploadPersonalNote - uploaded to GC.com"); + return true; + } + + } catch (final JSONException e) { + Log.e("Unknown exception with json wrap code", e); + } + Log.e("GCParser.uploadPersonalNote - cannot upload personal note"); + return false; + } + } diff --git a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java index d3a2959..ed44392 100644 --- a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java +++ b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java @@ -1,7 +1,7 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.Geocache; -import cgeo.geocaching.Settings; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.enumerations.CacheType; import android.graphics.Bitmap; @@ -21,7 +21,7 @@ public abstract class IconDecoder { private static final int CT_MEGAEVENT = 7; private static final int CT_CITO = 8; private static final int CT_WEBCAM = 9; - private static final int CT_WHEREIGO = 10; + private static final int CT_WHERIGO = 10; private static final int CT_VIRTUAL = 11; private static final int CT_LETTERBOX = 12; @@ -116,7 +116,7 @@ public abstract class IconDecoder { case CT_WEBCAM: cache.setType(CacheType.WEBCAM); return true; - case CT_WHEREIGO: + case CT_WHERIGO: cache.setType(CacheType.WHERIGO); return true; case CT_VIRTUAL: @@ -395,12 +395,12 @@ public abstract class IconDecoder { if (g < 71) { return CT_MYSTERY; } - return r < 153 ? CT_WHEREIGO : CT_WEBCAM; + return r < 153 ? CT_WHERIGO : CT_WEBCAM; } if (b < 167) { return r < 157 ? CT_TRADITIONAL : CT_WEBCAM; } - return CT_WHEREIGO; + return CT_WHERIGO; } if (g < 199) { if (r < 142) { @@ -450,7 +450,7 @@ public abstract class IconDecoder { if (b < 252) { if (r < 243) { if (r < 225) { - return CT_WHEREIGO; + return CT_WHERIGO; } if (b < 232) { if (g < 228) { @@ -459,14 +459,14 @@ public abstract class IconDecoder { return r < 231 ? CT_VIRTUAL : CT_TRADITIONAL; } if (r < 236) { - return CT_WHEREIGO; + return CT_WHERIGO; } - return r < 240 ? CT_WEBCAM : CT_WHEREIGO; + return r < 240 ? CT_WEBCAM : CT_WHERIGO; } if (g < 247) { return r < 245 ? CT_WEBCAM : CT_FOUND; } - return CT_WHEREIGO; + return CT_WHERIGO; } return CT_LETTERBOX; } diff --git a/main/src/cgeo/geocaching/connector/gc/Login.java b/main/src/cgeo/geocaching/connector/gc/Login.java index 7351311..4b4c93f 100644 --- a/main/src/cgeo/geocaching/connector/gc/Login.java +++ b/main/src/cgeo/geocaching/connector/gc/Login.java @@ -1,16 +1,16 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.R; -import cgeo.geocaching.Settings; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Cookies; import cgeo.geocaching.network.HtmlImage; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; -import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; @@ -52,9 +52,9 @@ public abstract class Login { "dd/MM/yyyy" }; - Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>(); + final Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>(); - for (String format : formats) { + for (final String format : formats) { map.put(format, new SimpleDateFormat(format, Locale.ENGLISH)); } @@ -66,7 +66,7 @@ public abstract class Login { } private static StatusCode login(boolean retry) { - final ImmutablePair<String, String> login = Settings.getLogin(); + final ImmutablePair<String, String> login = Settings.getGcLogin(); if (login == null || StringUtils.isEmpty(login.left) || StringUtils.isEmpty(login.right)) { Login.setActualStatus(cgeoapplication.getInstance().getString(R.string.err_login)); @@ -77,7 +77,7 @@ public abstract class Login { Login.setActualStatus(cgeoapplication.getInstance().getString(R.string.init_login_popup_working)); HttpResponse loginResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx"); String loginData = Network.getResponseData(loginResponse); - if (loginResponse != null && loginResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(loginData, GCConstants.PATTERN_MAINTENANCE)) { + if (loginResponse != null && loginResponse.getStatusLine().getStatusCode() == 503 && TextUtils.matches(loginData, GCConstants.PATTERN_MAINTENANCE)) { return StatusCode.MAINTENANCE; } @@ -147,9 +147,9 @@ public abstract class Login { } public static StatusCode logout() { - HttpResponse logoutResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx?RESET=Y&redir=http%3a%2f%2fwww.geocaching.com%2fdefault.aspx%3f"); - String logoutData = Network.getResponseData(logoutResponse); - if (logoutResponse != null && logoutResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(logoutData, GCConstants.PATTERN_MAINTENANCE)) { + final HttpResponse logoutResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx?RESET=Y&redir=http%3a%2f%2fwww.geocaching.com%2fdefault.aspx%3f"); + final String logoutData = Network.getResponseData(logoutResponse); + if (logoutResponse != null && logoutResponse.getStatusLine().getStatusCode() == 503 && TextUtils.matches(logoutData, GCConstants.PATTERN_MAINTENANCE)) { return StatusCode.MAINTENANCE; } @@ -205,17 +205,17 @@ public abstract class Login { setActualStatus(cgeoapplication.getInstance().getString(R.string.init_login_popup_ok)); // on every page except login page - setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME)); + setActualLoginStatus(TextUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME)); if (isActualLoginStatus()) { - setActualUserName(BaseUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???")); + setActualUserName(TextUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???")); int cachesCount = 0; try { - cachesCount = Integer.parseInt(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", "")); - } catch (NumberFormatException e) { + cachesCount = Integer.parseInt(TextUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", "")); + } catch (final NumberFormatException e) { Log.e("getLoginStatus: bad cache count", e); } setActualCachesFound(cachesCount); - Settings.setMemberStatus(BaseUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if ( page.contains(GCConstants.MEMBER_STATUS_RENEW) ) { Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); } @@ -223,7 +223,7 @@ public abstract class Login { } // login page - setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME_LOGIN_PAGE)); + setActualLoginStatus(TextUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME_LOGIN_PAGE)); if (isActualLoginStatus()) { setActualUserName(Settings.getUsername()); // number of caches found is not part of this page @@ -260,23 +260,23 @@ public abstract class Login { public static BitmapDrawable downloadAvatarAndGetMemberStatus() { try { - final String profile = BaseUtils.replaceWhitespace(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/"))); + final String profile = TextUtils.replaceWhitespace(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/"))); - Settings.setMemberStatus(BaseUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if (profile.contains(GCConstants.MEMBER_STATUS_RENEW)) { Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); } - setActualCachesFound(Integer.parseInt(BaseUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); + setActualCachesFound(Integer.parseInt(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); - final String avatarURL = BaseUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null); + final String avatarURL = TextUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null); if (null != avatarURL) { final HtmlImage imgGetter = new HtmlImage("", false, 0, false); return imgGetter.getDrawable(avatarURL); } // No match? There may be no avatar set by user. Log.d("No avatar set for user"); - } catch (Exception e) { + } catch (final Exception e) { Log.w("Error when retrieving user avatar", e); } return null; @@ -294,7 +294,7 @@ public abstract class Login { return; } - String customDate = BaseUtils.getMatch(result, GCConstants.PATTERN_CUSTOMDATE, true, null); + final String customDate = TextUtils.getMatch(result, GCConstants.PATTERN_CUSTOMDATE, true, null); if (null != customDate) { Settings.setGcCustomDate(customDate); } @@ -310,14 +310,14 @@ public abstract class Login { if (gcCustomDateFormats.containsKey(format)) { try { return gcCustomDateFormats.get(format).parse(trimmed); - } catch (ParseException e) { + } catch (final ParseException e) { } } - for (SimpleDateFormat sdf : gcCustomDateFormats.values()) { + for (final SimpleDateFormat sdf : gcCustomDateFormats.values()) { try { return sdf.parse(trimmed); - } catch (ParseException e) { + } catch (final ParseException e) { } } @@ -347,7 +347,7 @@ public abstract class Login { return true; } - for (String s : a) { + for (final String s : a) { if (StringUtils.isNotEmpty(s)) { return false; } @@ -373,24 +373,24 @@ public abstract class Login { if (matcherViewstateCount.find()) { try { count = Integer.parseInt(matcherViewstateCount.group(1)); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("getViewStates", e); } } - String[] viewstates = new String[count]; + final String[] viewstates = new String[count]; // Get the viewstates final MatcherWrapper matcherViewstates = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATES, page); while (matcherViewstates.find()) { - String sno = matcherViewstates.group(1); // number of viewstate + final String sno = matcherViewstates.group(1); // number of viewstate int no; if (StringUtils.isEmpty(sno)) { no = 0; } else { try { no = Integer.parseInt(sno); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("getViewStates", e); no = 0; } @@ -436,17 +436,17 @@ public abstract class Login { * @return */ public static String postRequestLogged(final String uri, final Parameters params) { - HttpResponse response = Network.postRequest(uri, params); - String data = Network.getResponseData(response); + final String data = Network.getResponseData(Network.postRequest(uri, params)); - if (!getLoginStatus(data)) { - if (login() == StatusCode.NO_ERROR) { - response = Network.postRequest(uri, params); - data = Network.getResponseData(response); - } else { - Log.i("Working as guest."); - } + if (getLoginStatus(data)) { + return data; + } + + if (login() == StatusCode.NO_ERROR) { + return Network.getResponseData(Network.postRequest(uri, params)); } + + Log.i("Working as guest."); return data; } @@ -458,26 +458,37 @@ public abstract class Login { * @return */ public static String getRequestLogged(final String uri, final Parameters params) { - final String data = Network.getResponseData(Network.getRequest(uri, params)); + final String data = Network.getResponseData(Network.getRequest(uri, params), canRemoveWhitespace(uri)); if (getLoginStatus(data)) { return data; } if (login() == StatusCode.NO_ERROR) { - return Network.getResponseData(Network.getRequest(uri, params)); + return Network.getResponseData(Network.getRequest(uri, params), canRemoveWhitespace(uri)); } Log.w("Working as guest."); return data; } + /** + * Unfortunately the cache details page contains user generated whitespace in the personal note, therefore we cannot + * remove the white space from cache details pages. + * + * @param uri + * @return + */ + private static boolean canRemoveWhitespace(final String uri) { + return !StringUtils.contains(uri, "cache_details"); + } + /** Get user session & session token from the Live Map. Needed for following requests */ public static String[] getMapTokens() { final HttpResponse response = Network.getRequest(GCConstants.URL_LIVE_MAP); final String data = Network.getResponseData(response); - final String userSession = BaseUtils.getMatch(data, GCConstants.PATTERN_USERSESSION, ""); - final String sessionToken = BaseUtils.getMatch(data, GCConstants.PATTERN_SESSIONTOKEN, ""); + final String userSession = TextUtils.getMatch(data, GCConstants.PATTERN_USERSESSION, ""); + final String sessionToken = TextUtils.getMatch(data, GCConstants.PATTERN_SESSIONTOKEN, ""); return new String[] { userSession, sessionToken }; } } diff --git a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java b/main/src/cgeo/geocaching/connector/gc/SearchHandler.java index 840cad1..45832e4 100644 --- a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java +++ b/main/src/cgeo/geocaching/connector/gc/SearchHandler.java @@ -102,7 +102,7 @@ public class SearchHandler extends Handler { imgHandler.sendEmptyMessage(0); } catch (IOException e) { - Log.e("Failed to download reCAPTCHA image"); + 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 0e5ffe7..ec90036 100644 --- a/main/src/cgeo/geocaching/connector/gc/Tile.java +++ b/main/src/cgeo/geocaching/connector/gc/Tile.java @@ -37,6 +37,7 @@ public class Tile { public static final int TILE_SIZE = 256; public static final int ZOOMLEVEL_MAX = 18; public static final int ZOOMLEVEL_MIN = 0; + public static final int ZOOMLEVEL_MIN_PERSONALIZED = 12; static final int[] NUMBER_OF_TILES = new int[ZOOMLEVEL_MAX - ZOOMLEVEL_MIN + 1]; static final int[] NUMBER_OF_PIXELS = new int[ZOOMLEVEL_MAX - ZOOMLEVEL_MIN + 1]; @@ -243,7 +244,7 @@ public class Tile { try { return response != null ? BitmapFactory.decodeStream(response.getEntity().getContent()) : null; } catch (IOException e) { - Log.e("cgBase.requestMapTile() " + e.getMessage()); + Log.e("Tile.requestMapTile() ", e); } return null; } @@ -252,6 +253,10 @@ public class Tile { return viewPort.contains(point); } + public Viewport getViewport() { + return viewPort; + } + /** * Calculate needed tiles for the given viewport to cover it with * max 2x2 tiles diff --git a/main/src/cgeo/geocaching/connector/oc/AttributeParser.java b/main/src/cgeo/geocaching/connector/oc/AttributeParser.java new file mode 100644 index 0000000..63bee77 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/AttributeParser.java @@ -0,0 +1,327 @@ +// This is a generated file, do not change manually! + +package cgeo.geocaching.connector.oc; + +import java.util.HashMap; +import java.util.Map; + +public class AttributeParser { + + private final static Map<String, Integer> attrMapDe; + private final static Map<String, Integer> attrMapPl; + + static { + attrMapDe = new HashMap<String, Integer>(); + attrMapPl = new HashMap<String, Integer>(); + + // last header line + attrMapDe.put("Listed at Opencaching only", 6); + attrMapDe.put("DostÄ™pna tylko na Opencaching", 6); + attrMapDe.put("Nur bei Opencaching logbar", 6); + attrMapDe.put("Solo loggeable en Opencaching", 6); + attrMapDe.put("Loggabile solo su Opencaching", 6); + attrMapPl.put("Near a Survey Marker", 54); + attrMapPl.put("W pobliżu punktu geodezyjnego", 54); + attrMapPl.put("Whereigo Cache", 55); + attrMapPl.put("Whereigo Cache", 55); + attrMapPl.put("Whereigo Cache", 55); + attrMapDe.put("Letterbox Cache", 8); + attrMapPl.put("Letterbox Cache", 56); + attrMapDe.put("Skrzynka typu Letterbox", 8); + attrMapPl.put("Skrzynka typu Letterbox", 56); + attrMapDe.put("Letterbox (benötigt Stempel)", 8); + attrMapPl.put("Letterbox (benötigt Stempel)", 56); + attrMapDe.put("Letterbox (necesita un estampador)", 8); + attrMapPl.put("Letterbox (necesita un estampador)", 56); + attrMapDe.put("Letterbox (richiede un timbro)", 8); + attrMapPl.put("Letterbox (richiede un timbro)", 56); + attrMapPl.put("GeoHotel", 43); + attrMapPl.put("GeoHotel", 43); + attrMapPl.put("GeoHotel", 43); + attrMapPl.put("Magnetic cache", 49); + attrMapPl.put("Przyczepiona magnesem", 49); + attrMapPl.put("magnetischer Cache", 49); + attrMapPl.put("Description contains an audio file", 50); + attrMapPl.put("Opis zawiera plik audio", 50); + attrMapPl.put("Offset cache", 51); + attrMapPl.put("Offset cache", 51); + attrMapPl.put("Peilungscache", 51); + attrMapPl.put("Garmin's wireless beacon", 52); + attrMapPl.put("Beacon - Garmin Chirp", 52); + attrMapPl.put("Funksignal – Garmin Chirp", 52); + attrMapPl.put("Dead Drop USB cache", 53); + attrMapPl.put("Dead Drop USB skrzynka", 53); + attrMapDe.put("Has a moving target", 31); + attrMapDe.put("bewegliches Ziel", 31); + attrMapDe.put("Objetivo en movimiento", 31); + attrMapDe.put("Oggetto in movimento", 31); + attrMapDe.put("Webcam Cache", 32); + attrMapDe.put("Webcam Cache", 32); + attrMapDe.put("Webcam Cache", 32); + attrMapDe.put("Webcam Cache", 32); + attrMapDe.put("Other cache type", 57); + attrMapDe.put("sonstiger Cachetyp", 57); + attrMapDe.put("Otro tipo de cache", 57); + attrMapDe.put("Altro tipo di cache", 57); + attrMapDe.put("Investigation required", 54); + attrMapDe.put("Recherche", 54); + attrMapDe.put("Investigación", 54); + attrMapDe.put("Ricerca", 54); + attrMapDe.put("Puzzle / Mystery", 55); + attrMapDe.put("Rätsel", 55); + attrMapDe.put("Puzzle / Misterio", 55); + attrMapDe.put("Puzzle / Mystery", 55); + attrMapDe.put("Arithmetical problem", 56); + attrMapDe.put("Rechenaufgabe", 56); + attrMapDe.put("Problema matemático", 56); + attrMapDe.put("Problema matematico", 56); + attrMapDe.put("Ask owner for start conditions", 58); + attrMapDe.put("Startbedingungen beim Owner erfragen", 58); + attrMapDe.put("Ask owner for start conditions", 58); + attrMapDe.put("Ask owner for start conditions", 58); + attrMapPl.put("Wheelchair accessible", 44); + attrMapPl.put("DostÄ™pna dla niepeÅ‚nosprawnych", 44); + attrMapPl.put("rollstuhltauglich", 44); + attrMapDe.put("Near the parking area", 24); + attrMapDe.put("nahe beim Auto", 24); + attrMapDe.put("Cerca de un Parking", 24); + attrMapDe.put("Vicino all'area di parcheggio", 24); + attrMapPl.put("Access only by walk", 84); + attrMapPl.put("DostÄ™pna tylko pieszo", 84); + attrMapDe.put("Long walk", 25); + attrMapDe.put("längere Wanderung", 25); + attrMapDe.put("Larga caminata", 25); + attrMapDe.put("Lunga camminata", 25); + attrMapDe.put("Swamp, marsh or wading", 26); + attrMapDe.put("sumpfig/matschiges Gelände / waten", 26); + attrMapDe.put("Pantano / terreno fangoso", 26); + attrMapDe.put("Palude o marcita", 26); + attrMapDe.put("Hilly area", 27); + attrMapDe.put("hügeliges Gelände", 27); + attrMapDe.put("Terreno montañoso", 27); + attrMapDe.put("Area collinare", 27); + attrMapDe.put("Some climbing (no gear needed)", 28); + attrMapDe.put("leichtes Klettern (ohne Ausrüstung)", 28); + attrMapDe.put("fácil de subir (sin equipo)", 28); + attrMapDe.put("Arrampicata (attrezzatura non necessaria)", 28); + attrMapDe.put("Swimming required", 29); + attrMapDe.put("Schwimmen erforderlich", 29); + attrMapDe.put("Requiere nadar", 29); + attrMapDe.put("Nuoto necessario", 29); + attrMapDe.put("Access or parking fee", 36); + attrMapDe.put("Zugangs- bzw. Parkentgelt", 36); + attrMapDe.put("Acceso o parking pagando", 36); + attrMapDe.put("Tassa di ingresso o di parcheggio", 36); + attrMapPl.put("Bikes allowed", 85); + attrMapPl.put("DostÄ™pna rowerem", 85); + attrMapPl.put("Hidden in natural surroundings (forests, mountains, etc.)", 60); + attrMapPl.put("Umiejscowiona na Å‚onie natury (lasy, góry, itp.)", 60); + attrMapPl.put("Historic site", 61); + attrMapPl.put("Miejsce historyczne", 61); + attrMapDe.put("Point of interest", 30); + attrMapDe.put("interessanter Ort", 30); + attrMapDe.put("Punto de interes", 30); + attrMapDe.put("Punto di interesse", 30); + attrMapDe.put("Hidden wihin enclosed rooms (caves, buildings etc.)", 33); + attrMapDe.put("in geschlossenen Räumen (Höhle, Gebäude, etc.)", 33); + attrMapDe.put("en espacios confinados (cuevas, edificios, etc)", 33); + attrMapDe.put("All'interno di stanze chiuse (caverne, edifici, ecc.)", 33); + attrMapDe.put("Hidden under water", 34); + attrMapDe.put("Im Wasser versteckt", 34); + attrMapDe.put("En el agua", 34); + attrMapDe.put("Nell'acqua", 34); + attrMapDe.put("Parking area nearby", 18); + attrMapDe.put("Parkplatz in der Nähe", 18); + attrMapDe.put("Parking cercano", 18); + attrMapDe.put("Parcheggio nei pressi", 18); + attrMapDe.put("Public transportation", 19); + attrMapDe.put("erreichbar mit ÖVM", 19); + attrMapDe.put("Transporte Público", 19); + attrMapDe.put("Trasporto pubblico", 19); + attrMapDe.put("Drinking water nearby", 20); + attrMapDe.put("Trinkwasser in der Nähe", 20); + attrMapDe.put("Agua potable en las cercanias", 20); + attrMapDe.put("Acqua potabile nei pressi", 20); + attrMapDe.put("Public restrooms nearby", 21); + attrMapDe.put("öffentliche Toilette in der Nähe", 21); + attrMapDe.put("Aseos públicos cercanos", 21); + attrMapDe.put("Bagni pubblici nei pressi", 21); + attrMapDe.put("Public phone nearby", 22); + attrMapDe.put("Telefon in der Nähe", 22); + attrMapDe.put("Teléfono Público en las cercanias", 22); + attrMapDe.put("Telefono pubblico nei pressi", 22); + attrMapDe.put("First aid available", 23); + attrMapDe.put("Erste Hilfe verfügbar", 23); + attrMapDe.put("Disponible socorro rapido", 23); + attrMapDe.put("Disponibile pronto soccorso", 23); + attrMapDe.put("Available 24/7", 38); + attrMapDe.put("rund um die Uhr machbar", 38); + attrMapDe.put("Disponible las 24 horas", 38); + attrMapDe.put("Disponibile 24 ore", 38); + attrMapDe.put("Not 24/7", 39); + attrMapPl.put("Not 24/7", 80); + attrMapDe.put("DostÄ™pna w okreÅ›lonych godzinach", 39); + attrMapPl.put("DostÄ™pna w okreÅ›lonych godzinach", 80); + attrMapDe.put("nur zu bestimmten Uhrzeiten", 39); + attrMapPl.put("nur zu bestimmten Uhrzeiten", 80); + attrMapDe.put("Sólo disponible a ciertas horas", 39); + attrMapPl.put("Sólo disponible a ciertas horas", 80); + attrMapDe.put("Disponibile solo in certi orari", 39); + attrMapPl.put("Disponibile solo in certi orari", 80); + attrMapDe.put("Not recommended at night", 40); + attrMapDe.put("nur tagüber", 40); + attrMapDe.put("solo por el dÃa", 40); + attrMapDe.put("solo di giorno", 40); + attrMapPl.put("Recommended at night", 91); + attrMapPl.put("Zalecane szukanie nocÄ…", 91); + attrMapPl.put("am besten nachts findbar", 91); + attrMapDe.put("Only at night", 1); + attrMapDe.put("nur bei Nacht", 1); + attrMapDe.put("Sólo por la noche", 1); + attrMapDe.put("Solo di notte", 1); + attrMapDe.put("All seasons", 42); + attrMapDe.put("ganzjähig zugänglich", 42); + attrMapDe.put("Todas las temporadas", 42); + attrMapDe.put("Tutte le stagioni", 42); + attrMapDe.put("Only available during specified seasons", 60); + attrMapDe.put("Nur zu bestimmten Zeiten im Jahr", 60); + attrMapDe.put("Sólo disponible durante las estaciones especificadas", 60); + attrMapDe.put("Disponibile solo in certe stagioni", 60); + attrMapDe.put("Breeding season / protected nature", 43); + attrMapDe.put("Brutsaison / Naturschutz", 43); + attrMapDe.put("Temporada de reproducción / protección de la naturaleza", 43); + attrMapDe.put("Stagione di riproduzione / natura protetta", 43); + attrMapDe.put("Available during winter", 44); + attrMapDe.put("schneesicheres Versteck", 44); + attrMapDe.put("Nieve en el escondite", 44); + attrMapDe.put("Luogo a prova di neve", 44); + attrMapDe.put("Not at high water level", 41); + attrMapDe.put("nicht bei Hochwasser oder Flut", 41); + attrMapDe.put("Compass required", 47); + attrMapPl.put("Compass required", 47); + attrMapDe.put("Potrzebny kompas", 47); + attrMapPl.put("Potrzebny kompas", 47); + attrMapDe.put("Kompass", 47); + attrMapPl.put("Kompass", 47); + attrMapDe.put("Brújula", 47); + attrMapPl.put("Brújula", 47); + attrMapDe.put("Bussola", 47); + attrMapPl.put("Bussola", 47); + attrMapPl.put("Take something to write", 48); + attrMapPl.put("Weź coÅ› do pisania", 48); + attrMapPl.put("You may need a shovel", 81); + attrMapPl.put("Potrzebna Å‚opatka", 81); + attrMapDe.put("Flashlight required", 48); + attrMapPl.put("Flashlight required", 82); + attrMapDe.put("Potrzebna latarka", 48); + attrMapPl.put("Potrzebna latarka", 82); + attrMapDe.put("Taschenlampe", 48); + attrMapPl.put("Taschenlampe", 82); + attrMapDe.put("Linterna", 48); + attrMapPl.put("Linterna", 82); + attrMapDe.put("Lampada tascabile", 48); + attrMapPl.put("Lampada tascabile", 82); + attrMapDe.put("Climbing gear required", 49); + attrMapDe.put("Kletterzeug", 49); + attrMapDe.put("Equipo de escalada", 49); + attrMapDe.put("Attrezzatura per arrampicata", 49); + attrMapDe.put("Cave equipment required", 50); + attrMapDe.put("Höhlenzeug", 50); + attrMapDe.put("Equipación para cuevas", 50); + attrMapDe.put("Attrezzatura per grotta", 50); + attrMapDe.put("Diving equipment required", 51); + attrMapDe.put("Taucherausrüstung", 51); + attrMapDe.put("Diving equipment", 51); + attrMapDe.put("Equipo de buceo", 51); + attrMapDe.put("Special tools required", 46); + attrMapPl.put("Special tools required", 83); + attrMapDe.put("Wymagany dodatkowy sprzÄ™t", 46); + attrMapPl.put("Wymagany dodatkowy sprzÄ™t", 83); + attrMapDe.put("spezielle Ausrüstung", 46); + attrMapPl.put("spezielle Ausrüstung", 83); + attrMapDe.put("Equipamiento especial", 46); + attrMapPl.put("Equipamiento especial", 83); + attrMapDe.put("Equipaggiamento speciale", 46); + attrMapPl.put("Equipaggiamento speciale", 83); + attrMapDe.put("Requires a boat", 52); + attrMapPl.put("Requires a boat", 86); + attrMapDe.put("Wymaga sprzÄ™tu pÅ‚ywajÄ…cego", 52); + attrMapPl.put("Wymaga sprzÄ™tu pÅ‚ywajÄ…cego", 86); + attrMapDe.put("Wasserfahrzeug", 52); + attrMapPl.put("Wasserfahrzeug", 86); + attrMapDe.put("Barca", 52); + attrMapPl.put("Barca", 86); + attrMapDe.put("Barca", 52); + attrMapPl.put("Barca", 86); + attrMapDe.put("No GPS required", 35); + attrMapDe.put("ohne GPS findbar", 35); + attrMapDe.put("Sin GPS", 35); + attrMapDe.put("Senza GPS", 35); + attrMapDe.put("Dangerous area", 9); + attrMapPl.put("Dangerous area", 90); + attrMapDe.put("Skrzynka niebezpieczna", 9); + attrMapPl.put("Skrzynka niebezpieczna", 90); + attrMapDe.put("gefährliches Gebiet", 9); + attrMapPl.put("gefährliches Gebiet", 90); + attrMapDe.put("Zona Peligrosa", 9); + attrMapPl.put("Zona Peligrosa", 90); + attrMapDe.put("Area pericolosa", 9); + attrMapPl.put("Area pericolosa", 90); + attrMapDe.put("Active railway nearby", 10); + attrMapDe.put("aktive Eisenbahnlinie in der Nähe", 10); + attrMapDe.put("Cerca del ferrocarril activo", 10); + attrMapDe.put("Ferrovia attiva nei pressi", 10); + attrMapDe.put("Cliff / Rocks", 11); + attrMapDe.put("Klippen / Felsen", 11); + attrMapDe.put("Acantilado / Rocas", 11); + attrMapDe.put("Scogliera / Rocce", 11); + attrMapDe.put("Hunting", 12); + attrMapDe.put("Jagdgebiet", 12); + attrMapDe.put("Zona de Caza", 12); + attrMapDe.put("Caccia", 12); + attrMapDe.put("Thorns", 13); + attrMapDe.put("Dornen", 13); + attrMapDe.put("Espinas", 13); + attrMapDe.put("Spine", 13); + attrMapDe.put("Ticks", 14); + attrMapDe.put("Zecken", 14); + attrMapDe.put("Garrapatas", 14); + attrMapDe.put("Zecche", 14); + attrMapDe.put("Abandoned mines", 15); + attrMapDe.put("Folgen des Bergbaus", 15); + attrMapDe.put("Mina abandonada", 15); + attrMapDe.put("Miniere abbandonate", 15); + attrMapDe.put("Poisonous plants", 16); + attrMapDe.put("giftige Pflanzen", 16); + attrMapDe.put("Planta venenosa", 16); + attrMapDe.put("Piante velenose", 16); + attrMapDe.put("Dangerous animals", 17); + attrMapDe.put("giftige/gefährliche Tiere", 17); + attrMapDe.put("Animales Peligrosos", 17); + attrMapDe.put("Animali pericolosi", 17); + attrMapPl.put("Quick cache", 40); + attrMapPl.put("Szybka skrzynka", 40); + attrMapDe.put("Overnight stay necessary", 37); + attrMapDe.put("Übernachtung erforderlich", 37); + attrMapDe.put("Necesario pernoctar", 37); + attrMapDe.put("Necessario pernottamento", 37); + attrMapPl.put("Take your children", 41); + attrMapPl.put("Można zabrać dzieci", 41); + attrMapDe.put("Suited for children (10-12 yo)", 59); + attrMapDe.put("kindgerecht (10-12 Jahre)", 59); + attrMapDe.put("Apto para niños (10-12 años)", 59); + attrMapDe.put("Suited for children (10-12 anni)", 59); + // first trailer line + + } + + public static int getOcDeId(final String name) { + + int result = 0; + + if (attrMapDe.containsKey(name)) { + result = attrMapDe.get(name); + } + return result; + } +} diff --git a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java deleted file mode 100644 index 621032f..0000000 --- a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java +++ /dev/null @@ -1,754 +0,0 @@ -package cgeo.geocaching.connector.oc; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.Image; -import cgeo.geocaching.LogEntry; -import cgeo.geocaching.R; -import cgeo.geocaching.Settings; -import cgeo.geocaching.cgeoapplication; -import cgeo.geocaching.connector.ConnectorFactory; -import cgeo.geocaching.connector.IConnector; -import cgeo.geocaching.connector.gc.GCConnector; -import cgeo.geocaching.enumerations.CacheAttribute; -import cgeo.geocaching.enumerations.CacheSize; -import cgeo.geocaching.enumerations.CacheType; -import cgeo.geocaching.enumerations.LogType; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.utils.Log; - -import org.apache.commons.lang3.StringUtils; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -import android.content.res.Resources; -import android.sax.Element; -import android.sax.EndElementListener; -import android.sax.EndTextElementListener; -import android.sax.RootElement; -import android.sax.StartElementListener; -import android.util.Xml; - -import java.io.IOException; -import java.io.InputStream; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class OC11XMLParser { - - private static final String[] MARKUP = new String[] { "p", "span" }; - private static Pattern STRIP_DATE = Pattern.compile("\\+0([0-9]){1}\\:00"); - private static Pattern LOCAL_URL = Pattern.compile("href=\"(.*)\""); - private static final int CACHE_PARSE_LIMIT = 250; - private static final Resources res = cgeoapplication.getInstance().getResources(); - - private static ImageHolder imageHolder = null; - - private static class CacheHolder { - public Geocache cache; - public String latitude; - public String longitude; - } - - private static class CacheLog { - public String id; - public String cacheId; - public LogEntry logEntry; - } - - private static class CacheDescription { - public String cacheId; - public String shortDesc; - public String desc; - public String hint; - } - - private static class ImageHolder { - public String url; - public String objectId; - protected String title; - protected boolean isSpoiler = false; - } - - private static Date parseFullDate(final String date) { - final SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); - ISO8601DATEFORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - final String strippedDate = STRIP_DATE.matcher(date).replaceAll("+0$100"); - try { - return ISO8601DATEFORMAT.parse(strippedDate); - } catch (ParseException e) { - Log.e("OC11XMLParser.parseFullDate", e); - } - return null; - } - - private static Date parseDayDate(final String date) { - final SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - ISO8601DATEFORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - final String strippedDate = STRIP_DATE.matcher(date).replaceAll("+0$100"); - try { - return ISO8601DATEFORMAT.parse(strippedDate); - } catch (ParseException e) { - Log.e("OC11XMLParser.parseDayDate", e); - } - return null; - } - - private static CacheSize getCacheSize(final String sizeId) { - try { - int size = Integer.parseInt(sizeId); - - switch (size) { - case 1: - return CacheSize.OTHER; - case 2: - return CacheSize.MICRO; - case 3: - return CacheSize.SMALL; - case 4: - return CacheSize.REGULAR; - case 5: - case 6: - return CacheSize.LARGE; - case 8: - return CacheSize.VIRTUAL; - default: - break; - } - } catch (NumberFormatException e) { - Log.e("OC11XMLParser.getCacheSize", e); - } - return CacheSize.NOT_CHOSEN; - } - - private static CacheType getCacheType(final String typeId) { - try { - int type = Integer.parseInt(typeId); - switch (type) { - case 1: // Other/unbekannter Cachetyp - return CacheType.UNKNOWN; - case 2: // Trad./normaler Cache - return CacheType.TRADITIONAL; - case 3: // Multi/Multicache - return CacheType.MULTI; - case 4: // Virt./virtueller Cache - return CacheType.VIRTUAL; - case 5: // ICam./Webcam-Cache - return CacheType.WEBCAM; - case 6: // Event/Event-Cache - return CacheType.EVENT; - case 7: // Quiz/Rätselcache - return CacheType.MYSTERY; - case 8: // Math/Mathe-/Physikcache - return CacheType.MYSTERY; - case 9: // Moving/beweglicher Cache - return CacheType.VIRTUAL; - case 10: // Driv./Drive-In - return CacheType.TRADITIONAL; - default: - return CacheType.UNKNOWN; - } - } catch (NumberFormatException e) { - Log.e("OC11XMLParser.getCacheType", e); - } - return CacheType.UNKNOWN; - } - - private static LogType getLogType(final int typeId) { - switch (typeId) { - case 1: - return LogType.FOUND_IT; - case 2: - return LogType.DIDNT_FIND_IT; - case 3: - return LogType.NOTE; - case 7: - return LogType.ATTENDED; - case 8: - return LogType.WILL_ATTEND; - default: - return LogType.UNKNOWN; - } - } - - private static void setCacheStatus(final int statusId, final Geocache cache) { - switch (statusId) { - case 1: - cache.setArchived(false); - cache.setDisabled(false); - break; - case 2: - cache.setArchived(false); - cache.setDisabled(true); - break; - default: - cache.setArchived(true); - cache.setDisabled(false); - break; - } - } - - private static void resetCache(final CacheHolder cacheHolder) { - cacheHolder.cache = new Geocache(null); - cacheHolder.cache.setReliableLatLon(true); - cacheHolder.cache.setDescription(StringUtils.EMPTY); - cacheHolder.latitude = "0.0"; - cacheHolder.longitude = "0.0"; - } - - private static void resetLog(final CacheLog log) { - log.cacheId = StringUtils.EMPTY; - log.logEntry = new LogEntry("", 0, LogType.UNKNOWN, ""); - } - - private static void resetDesc(final CacheDescription desc) { - desc.cacheId = StringUtils.EMPTY; - desc.shortDesc = StringUtils.EMPTY; - desc.desc = StringUtils.EMPTY; - desc.hint = StringUtils.EMPTY; - } - - private static int attributeId; - - public static Collection<Geocache> parseCaches(final InputStream stream) throws IOException { - // parse and return caches without filtering - return parseCaches(stream, true); - } - - public static Collection<Geocache> parseCachesFiltered(final InputStream stream) throws IOException { - // parse caches and filter result - return parseCaches(stream, false); - } - - private static Collection<Geocache> parseCaches(final InputStream stream, boolean ignoreFiltersIn) throws IOException { - - final Map<String, Geocache> caches = new HashMap<String, Geocache>(); - final Map<String, LogEntry> logs = new HashMap<String, LogEntry>(); - - final CacheHolder cacheHolder = new CacheHolder(); - final CacheLog logHolder = new CacheLog(); - final CacheDescription descHolder = new CacheDescription(); - - final RootElement root = new RootElement("oc11xml"); - final Element cacheNode = root.getChild("cache"); - - final boolean ignoreFilters = ignoreFiltersIn; - - // cache - cacheNode.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - resetCache(cacheHolder); - } - - }); - - cacheNode.setEndElementListener(new EndElementListener() { - - @Override - public void end() { - Geocache cache = cacheHolder.cache; - Geopoint coords = new Geopoint(cacheHolder.latitude, cacheHolder.longitude); - cache.setCoords(coords); - if (caches.size() < CACHE_PARSE_LIMIT && isValid(cache) && (ignoreFilters || !isExcluded(cache))) { - cache.setDetailedUpdatedNow(); - caches.put(cache.getCacheId(), cache); - } - } - - private boolean isExcluded(Geocache cache) { - if (cache.isArchived()) { - return true; - } - if (cache.isDisabled() && Settings.isExcludeDisabledCaches()) { - return true; - } - if ((cache.isFound() || cache.isOwner()) && Settings.isExcludeMyCaches()) { - return true; - } - return !Settings.getCacheType().contains(cache); - } - - private boolean isValid(Geocache cache) { - return StringUtils.isNotBlank(cache.getGeocode()) && !cache.getCoords().equals(Geopoint.ZERO); - } - }); - - // cache.id - cacheNode.getChild("id").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - cacheHolder.cache.setCacheId(body); - } - }); - - // cache.longitude - cacheNode.getChild("longitude").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - String longitude = body.trim(); - if (StringUtils.isNotBlank(longitude)) { - cacheHolder.longitude = longitude; - } - } - }); - - // cache.latitude - cacheNode.getChild("latitude").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - String latitude = body.trim(); - if (StringUtils.isNotBlank(latitude)) { - cacheHolder.latitude = latitude; - } - } - }); - - // cache.name - cacheNode.getChild("name").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - cacheHolder.cache.setName(content); - } - }); - - // cache.waypoints[oc] - cacheNode.getChild("waypoints").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("oc") > -1) { - cacheHolder.cache.setGeocode(attrs.getValue("oc")); - } - if (attrs.getIndex("gccom") > -1) { - String gccode = attrs.getValue("gccom"); - if (!StringUtils.isBlank(gccode)) { - cacheHolder.cache.setDescription(res.getString(R.string.cache_listed_on, GCConnector.getInstance().getName()) + ": <a href=\"http://coord.info/" + gccode + "\">" + gccode + "</a><br /><br />"); - } - } - } - }); - - // cache.type[id] - cacheNode.getChild("type").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("id") > -1) { - final String typeId = attrs.getValue("id"); - cacheHolder.cache.setType(getCacheType(typeId)); - } - } - }); - - // cache.status[id] - cacheNode.getChild("status").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("id") > -1) { - try { - final int statusId = Integer.parseInt(attrs.getValue("id")); - setCacheStatus(statusId, cacheHolder.cache); - } catch (NumberFormatException e) { - Log.w(String.format("Failed to parse status of cache '%s'.", cacheHolder.cache.getGeocode())); - } - } - } - }); - - // cache.size[id] - cacheNode.getChild("size").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("id") > -1) { - final String typeId = attrs.getValue("id"); - cacheHolder.cache.setSize(getCacheSize(typeId)); - } - } - }); - - // cache.difficulty - cacheNode.getChild("difficulty").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - try { - cacheHolder.cache.setDifficulty(Float.valueOf(content)); - } catch (NumberFormatException e) { - Log.e("OC11XMLParser: unknown difficulty " + content, e); - } - } - }); - - // cache.terrain - cacheNode.getChild("terrain").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - try { - cacheHolder.cache.setTerrain(Float.valueOf(content)); - } catch (NumberFormatException e) { - Log.e("OC11XMLParser: unknown terrain " + content, e); - } - } - }); - - // cache.datehidden - cacheNode.getChild("datehidden").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - cacheHolder.cache.setHidden(parseFullDate(content)); - } - }); - - // cache.userid - final Element useridNode = cacheNode.getChild("userid"); - - useridNode.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - if (attributes.getIndex("id") > -1) { - cacheHolder.cache.setOwnerUserId(attributes.getValue("id")); - } - } - }); - - useridNode.setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - cacheHolder.cache.setOwnerDisplayName(body); - } - }); - - // cache.attributes.attribute - final Element attributeNode = cacheNode.getChild("attributes").getChild("attribute"); - - attributeNode.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - if (attributes.getIndex("id") > -1) { - try { - attributeId = Integer.parseInt(attributes.getValue("id")); - } catch (NumberFormatException e) { - Log.w(String.format("Failed to parse attribute id of cache '%s'.", cacheHolder.cache.getGeocode())); - } - } - } - }); - - attributeNode.setEndTextElementListener(new EndTextElementListener() { - @Override - public void end(String body) { - CacheAttribute attribute = CacheAttribute.getByOcId(attributeId); - if (attribute != null) { - // semantic of attributes on opencaching is always "yes" - cacheHolder.cache.getAttributes().add(attribute.getAttributeName(true)); - } - else { - if (StringUtils.isNotBlank(body)) { - cacheHolder.cache.getAttributes().add(body.trim()); - } - } - } - }); - - // cachedesc - final Element cacheDesc = root.getChild("cachedesc"); - - cacheDesc.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - resetDesc(descHolder); - } - }); - - cacheDesc.setEndElementListener(new EndElementListener() { - - @Override - public void end() { - final Geocache cache = caches.get(descHolder.cacheId); - if (cache != null) { - cache.setShortDescription(descHolder.shortDesc); - cache.setDescription(cache.getDescription() + descHolder.desc); - cache.setHint(descHolder.hint); - } - } - }); - - // cachedesc.cacheid - cacheDesc.getChild("cacheid").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - descHolder.cacheId = body; - } - }); - - // cachedesc.desc - cacheDesc.getChild("shortdesc").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - descHolder.shortDesc = linkify(stripMarkup(content)); - } - }); - - // cachedesc.desc - cacheDesc.getChild("desc").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - descHolder.desc = linkify(stripMarkup(content)); - } - }); - - // cachedesc.hint - cacheDesc.getChild("hint").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - descHolder.hint = body.trim(); - } - }); - - // cachelog - final Element cacheLog = root.getChild("cachelog"); - - cacheLog.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - resetLog(logHolder); - } - }); - - cacheLog.setEndElementListener(new EndElementListener() { - - @Override - public void end() { - final Geocache cache = caches.get(logHolder.cacheId); - if (cache != null && logHolder.logEntry.type != LogType.UNKNOWN) { - logs.put(logHolder.id, logHolder.logEntry); - cache.getLogs().add(0, logHolder.logEntry); - if ((logHolder.logEntry.type == LogType.FOUND_IT || logHolder.logEntry.type == LogType.ATTENDED) - && StringUtils.equalsIgnoreCase(logHolder.logEntry.author, Settings.getOCConnectorUserName())) { - cache.setFound(true); - cache.setVisitedDate(logHolder.logEntry.date); - } - } - } - }); - - // cachelog.id - cacheLog.getChild("id").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - logHolder.id = StringUtils.trim(body); - } - }); - - // cachelog.cacheid - cacheLog.getChild("cacheid").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - logHolder.cacheId = body; - } - }); - - // cachelog.date - cacheLog.getChild("date").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - try { - logHolder.logEntry.date = parseDayDate(body).getTime(); - } catch (NullPointerException e) { - Log.w("Failed to parse log date", e); - } - } - }); - - // cachelog.logtype - cacheLog.getChild("logtype").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("id") > -1) { - final String id = attrs.getValue("id"); - try { - final int typeId = Integer.parseInt(id); - logHolder.logEntry.type = getLogType(typeId); - } catch (NumberFormatException e) { - Log.e("OC11XMLParser, unknown logtype " + id, e); - } - } - } - }); - - // cachelog.userid - cacheLog.getChild("userid").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String finderName) { - logHolder.logEntry.author = finderName; - } - }); - - // cachelog.text - cacheLog.getChild("text").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String logText) { - logHolder.logEntry.log = stripMarkup(logText); - } - }); - - // pictures - final Element picture = root.getChild("picture"); - - picture.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - imageHolder = new ImageHolder(); - } - }); - - picture.setEndElementListener(new EndElementListener() { - - @Override - public void end() { - if (imageHolder.isSpoiler) { - final Geocache cache = caches.get(imageHolder.objectId); - if (cache != null) { - Image spoiler = new Image(imageHolder.url, imageHolder.title); - cache.addSpoiler(spoiler); - } - } - else { - final LogEntry log = logs.get(imageHolder.objectId); - if (log != null) { - log.addLogImage(new Image(imageHolder.url, imageHolder.title)); - } - } - } - }); - - // picture.object - picture.getChild("object").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - imageHolder.objectId = StringUtils.trim(body); - } - }); - - // picture.title - picture.getChild("title").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - imageHolder.title = StringUtils.trim(body); - } - }); - - // picture.url - picture.getChild("url").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - imageHolder.url = StringUtils.trim(body); - } - }); - - // picture.attributes - picture.getChild("attributes").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - if (attributes.getIndex("spoiler") > -1) { - String spoiler = attributes.getValue("spoiler"); - imageHolder.isSpoiler = ("1".equals(spoiler)); - } - } - }); - - try { - Xml.parse(stream, Xml.Encoding.UTF_8, root.getContentHandler()); - return caches.values(); - } catch (SAXException e) { - Log.e("Cannot parse .gpx file as oc11xml: could not parse XML", e); - return null; - } - } - - /** - * Converts local links to absolute links targeting the OC website. - */ - private static String linkify(String input) { - String result = input; - Matcher matcher = LOCAL_URL.matcher(result); - while (matcher.find()) { - String url = matcher.group(1); - if (!url.contains(":/")) { - IConnector ocConnector = ConnectorFactory.getConnector("OCXXX"); - String prefix = "http://" + ocConnector.getHost() + "/"; - result = StringUtils.replace(result, url, prefix + url); - matcher = LOCAL_URL.matcher(result); - } - } - return result; - } - - /** - * Removes unneeded markup. Log texts are typically encapsulated in paragraph tags which lead to more empty space on - * rendering. - */ - protected static String stripMarkup(String input) { - if (!StringUtils.startsWith(input, "<")) { - return input; - } - String result = input.trim(); - for (String tagName : MARKUP) { - final String startTag = "<" + tagName + ">"; - if (StringUtils.startsWith(result, startTag)) { - final String endTag = "</" + tagName + ">"; - if (StringUtils.endsWith(result, endTag)) { - String inner = result.substring(startTag.length(), result.length() - endTag.length()).trim(); - String nested = stripMarkup(inner); - if (!nested.contains(startTag)) { - result = nested; - } - } - } - } - return result; - } -}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java index 69cf8a4..4f365ec 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java @@ -7,13 +7,31 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.CryptUtils; +import org.apache.commons.lang3.StringUtils; + public class OCApiConnector extends OCConnector implements ISearchByGeocode { + // Levels of Okapi we support + // oldapi is around rev 500 + // current is from rev 798 onwards + public enum ApiSupport { + oldapi, + current + } + + // Levels of OAuth-Authentication we support + public enum OAuthLevel { + Level1, + Level3 + } + private final String cK; + private final ApiSupport apiSupport; - public OCApiConnector(String name, String host, String prefix, String cK) { + public OCApiConnector(String name, String host, String prefix, String cK, ApiSupport apiSupport) { super(name, host, prefix); this.cK = cK; + this.apiSupport = apiSupport; } public void addAuthentication(final Parameters params) { @@ -23,7 +41,7 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { @Override public String getLicenseText(final Geocache cache) { // NOT TO BE TRANSLATED - return "<a href=\"" + getCacheUrl(cache) + "\">" + getName() + "</a> data licensed under the Creative Commons BY-SA 3.0 License"; + return "© " + cache.getOwnerDisplayName() + ", <a href=\"" + getCacheUrl(cache) + "\">" + getName() + "</a>, CC-BY-NC-ND, alle Logeinträge © jeweiliger Autor"; } @Override @@ -40,4 +58,23 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { // currently always active, but only for details download return true; } + + @SuppressWarnings("static-method") + public OAuthLevel getSupportedAuthLevel() { + return OAuthLevel.Level1; + } + + public String getCK() { + return CryptUtils.rot13(cK); + } + + @SuppressWarnings("static-method") + public String getCS() { + return StringUtils.EMPTY; + } + + public ApiSupport getApiSupport() { + return apiSupport; + } + } diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java new file mode 100644 index 0000000..5ca2d28 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java @@ -0,0 +1,139 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.SearchResult; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.cgData; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.capability.ILogin; +import cgeo.geocaching.connector.capability.ISearchByCenter; +import cgeo.geocaching.connector.capability.ISearchByViewPort; +import cgeo.geocaching.connector.oc.UserInfo.UserInfoStatus; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.utils.CryptUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.content.Context; +import android.os.Handler; + +public class OCApiLiveConnector extends OCApiConnector implements ISearchByCenter, ISearchByViewPort, ILogin { + + private String cS; + private UserInfo userInfo = new UserInfo(StringUtils.EMPTY, 0, UserInfoStatus.NOT_RETRIEVED); + + public OCApiLiveConnector(String name, String host, String prefix, int cKResId, int cSResId, ApiSupport apiSupport) { + super(name, host, prefix, CryptUtils.rot13(cgeoapplication.getInstance().getResources().getString(cKResId)), apiSupport); + + cS = CryptUtils.rot13(cgeoapplication.getInstance().getResources().getString(cSResId)); + } + + @Override + public boolean isActivated() { + return Settings.isOCConnectorActive(); + } + + @Override + public SearchResult searchByViewport(Viewport viewport, String[] tokens) { + return new SearchResult(OkapiClient.getCachesBBox(viewport, this)); + } + + @Override + public SearchResult searchByCenter(Geopoint center) { + + return new SearchResult(OkapiClient.getCachesAround(center, this)); + } + + @Override + public OAuthLevel getSupportedAuthLevel() { + // TODO the tokens must be available connector specific + if (StringUtils.isNotBlank(Settings.getOCDETokenPublic()) && StringUtils.isNotBlank(Settings.getOCDETokenSecret())) { + return OAuthLevel.Level3; + } + return OAuthLevel.Level1; + } + + @Override + public String getCS() { + return CryptUtils.rot13(cS); + } + + @Override + public boolean supportsWatchList() { + return true; + } + + @Override + public boolean addToWatchlist(Geocache cache) { + final boolean added = OkapiClient.setWatchState(cache, true, this); + + if (added) { + cgData.saveChangedCache(cache); + } + + return added; + } + + @Override + public boolean removeFromWatchlist(Geocache cache) { + final boolean removed = OkapiClient.setWatchState(cache, false, this); + + if (removed) { + cgData.saveChangedCache(cache); + } + + return removed; + } + + @Override + public boolean supportsLogging() { + return true; + } + + @Override + public ILoggingManager getLoggingManager(Activity activity, Geocache cache) { + return new OkapiLoggingManager(activity, this, cache); + } + + @Override + public boolean canLog(Geocache cache) { + return true; + } + + public boolean supportsPersonalization() { + return getSupportedAuthLevel() == OAuthLevel.Level3; + } + + @Override + public boolean login(Handler handler, Context fromActivity) { + if (supportsPersonalization()) { + userInfo = OkapiClient.getUserInfo(this); + } else { + userInfo = new UserInfo(StringUtils.EMPTY, 0, UserInfoStatus.NOT_SUPPORTED); + } + return userInfo.getStatus() == UserInfoStatus.SUCCESSFUL; + } + + @Override + public String getUserName() { + return userInfo.getName(); + } + + @Override + public int getCachesFound() { + return userInfo.getFinds(); + } + + @Override + public String getLoginStatusString() { + return cgeoapplication.getInstance().getString(userInfo.getStatus().resId); + } + + @Override + public boolean isLoggedIn() { + return userInfo.getStatus() == UserInfoStatus.SUCCESSFUL; + } +} diff --git a/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java b/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java new file mode 100644 index 0000000..08d796e --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java @@ -0,0 +1,107 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.R; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.network.OAuthAuthorizationActivity; + +import org.apache.commons.lang3.tuple.ImmutablePair; + +public class OCAuthorizationActivity extends OAuthAuthorizationActivity { + + public OCAuthorizationActivity() { + super("www.opencaching.de", + "/okapi/services/oauth/request_token", + "/okapi/services/oauth/authorize", + "/okapi/services/oauth/access_token", + false, + cgeoapplication.getInstance().getResources().getString(R.string.oc_de_okapi_consumer_key), + cgeoapplication.getInstance().getResources().getString(R.string.oc_de_okapi_consumer_secret)); + } + + @Override + protected ImmutablePair<String, String> getTempToken() { + return Settings.getTempOCDEToken(); + } + + @Override + protected void setTempTokens(String tokenPublic, String tokenSecret) { + Settings.setOCDETempTokens(tokenPublic, tokenSecret); + } + + @Override + protected void setTokens(String tokenPublic, String tokenSecret, boolean enable) { + Settings.setOCDETokens(tokenPublic, tokenSecret, enable); + } + + @Override + protected String getAuthTitle() { + return res.getString(R.string.auth_ocde); + } + + @Override + protected String getAuthAgain() { + return res.getString(R.string.auth_again_oc); + } + + @Override + protected String getErrAuthInitialize() { + return res.getString(R.string.err_auth_initialize); + } + + @Override + protected String getAuthStart() { + return res.getString(R.string.auth_start_oc); + } + + @Override + protected String getAuthDialogCompleted() { + return res.getString(R.string.auth_dialog_completed_oc, getAuthTitle()); + } + + @Override + protected String getErrAuthProcess() { + return res.getString(R.string.err_auth_process); + } + + @Override + protected String getAuthDialogWait() { + return res.getString(R.string.auth_dialog_wait_oc, getAuthTitle()); + } + + @Override + protected String getAuthDialogPinTitle() { + return res.getString(R.string.auth_dialog_pin_title_oc); + } + + @Override + protected String getAuthDialogPinMessage() { + return res.getString(R.string.auth_dialog_pin_message_oc, getAuthTitle()); + } + + @Override + protected String getAboutAuth1() { + return res.getString(R.string.about_auth_1_oc, getAuthTitle()); + } + + @Override + protected String getAboutAuth2() { + return res.getString(R.string.about_auth_2_oc, getAuthTitle(), getAuthTitle()); + } + + @Override + protected String getAuthAuthorize() { + return res.getString(R.string.auth_authorize_oc); + } + + @Override + protected String getAuthPinHint() { + return res.getString(R.string.auth_pin_hint_oc, getAuthTitle()); + } + + @Override + protected String getAuthFinish() { + return res.getString(R.string.auth_finish_oc); + } + +} diff --git a/main/src/cgeo/geocaching/connector/oc/OCConnector.java b/main/src/cgeo/geocaching/connector/oc/OCConnector.java index 62dfb4c..29cdd10 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCConnector.java @@ -2,8 +2,8 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; +import cgeo.geocaching.R; import cgeo.geocaching.connector.AbstractConnector; -import cgeo.geocaching.enumerations.CacheRealm; import java.util.regex.Pattern; @@ -12,7 +12,7 @@ public class OCConnector extends AbstractConnector { private final String host; private final String name; private final Pattern codePattern; - private static final Pattern gpxZipFilePattern = Pattern.compile("oc[a-z]{2,3}\\d{5,}\\.zip", Pattern.CASE_INSENSITIVE); + private static final Pattern GPX_ZIP_FILE_PATTERN = Pattern.compile("oc[a-z]{2,3}\\d{5,}\\.zip", Pattern.CASE_INSENSITIVE); public OCConnector(final String name, final String host, final String prefix) { this.name = name; @@ -45,7 +45,7 @@ public class OCConnector extends AbstractConnector { @Override public boolean isZippedGPXFile(String fileName) { - return gpxZipFilePattern.matcher(fileName).matches(); + return GPX_ZIP_FILE_PATTERN.matcher(fileName).matches(); } @Override @@ -59,8 +59,11 @@ public class OCConnector extends AbstractConnector { } @Override - public CacheRealm getCacheRealm() { - return CacheRealm.OC; + public int getCacheMapMarkerId(boolean disabled) { + if (disabled) { + return R.drawable.marker_disabled_oc; + } + return R.drawable.marker_oc; } } diff --git a/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java deleted file mode 100644 index 43fdcfc..0000000 --- a/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java +++ /dev/null @@ -1,67 +0,0 @@ -package cgeo.geocaching.connector.oc; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.ICache; -import cgeo.geocaching.SearchResult; -import cgeo.geocaching.Settings; -import cgeo.geocaching.connector.capability.ISearchByCenter; -import cgeo.geocaching.connector.capability.ISearchByGeocode; -import cgeo.geocaching.connector.capability.ISearchByViewPort; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.geopoint.Viewport; -import cgeo.geocaching.ui.Formatter; -import cgeo.geocaching.utils.CancellableHandler; - -import org.apache.commons.lang3.StringUtils; - -public class OCXMLApiConnector extends OCConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort { - - private final static double SEARCH_DISTANCE_LIMIT = 15.0; - private final static double NEARBY_SEARCH_DISTANCE = 5.0; - - public OCXMLApiConnector(String name, String host, String prefix) { - super(name, host, prefix); - } - - @Override - public SearchResult searchByGeocode(final String geocode, final String guid, CancellableHandler handler) { - final Geocache cache = OCXMLClient.getCache(geocode); - if (cache == null) { - return null; - } - return new SearchResult(cache); - } - - @Override - public SearchResult searchByCenter(final Geopoint center) { - return new SearchResult(OCXMLClient.getCachesAround(center, NEARBY_SEARCH_DISTANCE)); - } - - @Override - public SearchResult searchByViewport(final Viewport viewport, final String[] tokens) { - final Geopoint center = viewport.getCenter(); - double distance = center.distanceTo(viewport.bottomLeft) * 1.15; - if (distance > SEARCH_DISTANCE_LIMIT) { - distance = SEARCH_DISTANCE_LIMIT; - } - return new SearchResult(OCXMLClient.getCachesAround(center, distance)); - } - - @Override - public boolean isActivated() { - // currently only tested and working with oc.de - return Settings.isOCConnectorActive(); - } - - @Override - public boolean isOwner(ICache cache) { - return StringUtils.equalsIgnoreCase(cache.getOwnerDisplayName(), Settings.getOCConnectorUserName()); - } - - @Override - public String getLicenseText(Geocache cache) { - // not to be translated - return "© " + cache.getOwnerDisplayName() + ", " + "<a href=\"" + getCacheUrl(cache) + "\">www.opencaching.de</a>, CC-BY-NC-ND, Stand: " + Formatter.formatFullDate(cache.getUpdated()); - } - -} diff --git a/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java b/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java deleted file mode 100644 index 6767b48..0000000 --- a/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java +++ /dev/null @@ -1,115 +0,0 @@ -package cgeo.geocaching.connector.oc; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.cgData; -import cgeo.geocaching.connector.ConnectorFactory; -import cgeo.geocaching.connector.IConnector; -import cgeo.geocaching.enumerations.LoadFlags; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.geopoint.GeopointFormatter; -import cgeo.geocaching.network.Network; -import cgeo.geocaching.network.Parameters; -import cgeo.geocaching.utils.Log; - -import ch.boye.httpclientandroidlib.HttpResponse; - -import org.apache.commons.lang3.StringUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; -import java.util.zip.GZIPInputStream; - -public class OCXMLClient { - - private static final String SERVICE_CACHE = "/xml/ocxml11.php"; - - // Url for single cache requests - // http://www.opencaching.de/xml/ocxml11.php?modifiedsince=20060320000000&user=0&cache=1&cachedesc=1&cachelog=1&picture=1&removedobject=0&session=0&doctype=0&charset=utf-8&wp=OCC9BE - - public static Geocache getCache(final String geoCode) { - try { - final Parameters params = getOCXmlQueryParameters(true, true, true); - params.put("wp", geoCode); - final InputStream data = request(ConnectorFactory.getConnector(geoCode), SERVICE_CACHE, params); - - if (data == null) { - return null; - } - - Collection<Geocache> caches = OC11XMLParser.parseCaches(new GZIPInputStream(data)); - if (caches.iterator().hasNext()) { - Geocache cache = caches.iterator().next(); - cgData.saveCache(cache, LoadFlags.SAVE_ALL); - return cache; - } - return null; - } catch (IOException e) { - Log.e("Error parsing cache '" + geoCode + "'", e); - return null; - } - } - - public static Collection<Geocache> getCachesAround(final Geopoint center, final double distance) { - try { - final Parameters params = getOCXmlQueryParameters(false, false, false); - params.put("lat", GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, center)); - params.put("lon", GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, center)); - params.put("distance", String.format(Locale.US, "%f", distance)); - final InputStream data = request(ConnectorFactory.getConnector("OCXXX"), SERVICE_CACHE, params); - - if (data == null) { - return Collections.emptyList(); - } - - return OC11XMLParser.parseCachesFiltered(new GZIPInputStream(data)); - } catch (IOException e) { - Log.e("Error parsing nearby search result", e); - return Collections.emptyList(); - } - } - - private static InputStream request(final IConnector connector, final String service, final Parameters params) { - if (connector == null) { - return null; - } - if (!(connector instanceof OCXMLApiConnector)) { - return null; - } - - final String host = connector.getHost(); - if (StringUtils.isBlank(host)) { - return null; - } - - final String uri = "http://" + host + service; - HttpResponse resp = Network.getRequest(uri, params); - if (resp != null) { - try { - return resp.getEntity().getContent(); - } catch (IllegalStateException e) { - // fall through and return null - } catch (IOException e) { - // fall through and return null - } - } - return null; - } - - private static Parameters getOCXmlQueryParameters(final boolean withDescription, final boolean withLogs, final boolean withImages) { - return new Parameters("modifiedsince", "20000101000000", - "user", "0", - "cache", "1", - "cachedesc", withDescription ? "1" : "0", - "cachelog", withLogs ? "1" : "0", - "picture", withImages ? "1" : "0", - "removedobject", "0", - "session", "0", - "doctype", "0", - "charset", "utf-8", - "zip", "gzip", - "picturefromcachelog", withImages ? "1" : "0"); - } -} diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 0673605..6959adf 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -3,17 +3,31 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.Geocache; import cgeo.geocaching.Image; import cgeo.geocaching.LogEntry; +import cgeo.geocaching.R; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgData; +import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; +import cgeo.geocaching.connector.LogResult; +import cgeo.geocaching.connector.gc.GCConnector; +import cgeo.geocaching.connector.oc.OCApiConnector.ApiSupport; +import cgeo.geocaching.connector.oc.OCApiConnector.OAuthLevel; +import cgeo.geocaching.connector.oc.UserInfo.UserInfoStatus; +import cgeo.geocaching.enumerations.CacheAttribute; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.enumerations.WaypointType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.network.Network; +import cgeo.geocaching.network.OAuth; import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; @@ -22,17 +36,42 @@ import org.json.JSONException; import org.json.JSONObject; import android.net.Uri; -import android.text.Html; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; import java.util.Date; import java.util.EnumSet; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; -final public class OkapiClient { +final class OkapiClient { + + private static final char SEPARATOR = '|'; + private static final String SEPARATOR_STRING = Character.toString(SEPARATOR); + private static final SimpleDateFormat LOG_DATE_FORMAT; + static { + LOG_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", Locale.US); + LOG_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + private static final SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()); + + private static final String CACHE_ATTRNAMES = "attrnames"; + private static final String WPT_LOCATION = "location"; + private static final String WPT_DESCRIPTION = "description"; + private static final String WPT_TYPE = "type"; + private static final String WPT_NAME = "name"; + private static final String CACHE_IS_WATCHED = "is_watched"; + private static final String CACHE_WPTS = "alt_wpts"; + 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_VOTES = "rating_votes"; private static final String CACHE_NOTFOUNDS = "notfounds"; @@ -55,6 +94,8 @@ final public class OkapiClient { private static final String CACHE_LOCATION = "location"; private static final String CACHE_NAME = "name"; private static final String CACHE_CODE = "code"; + private static final String CACHE_REQ_PASSWORD = "req_passwd"; + private static final String CACHE_MY_NOTES = "my_notes"; private static final String LOG_TYPE = "type"; private static final String LOG_COMMENT = "comment"; @@ -62,15 +103,35 @@ final public class OkapiClient { private static final String LOG_USER = "user"; private static final String USER_USERNAME = "username"; + private static final String USER_CACHES_FOUND = "caches_found"; + private static final String USER_INFO_FIELDS = "username|caches_found"; + + // 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"; + 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|date_hidden|alt_wpts|attrnames|req_passwd"; + private static final String SERVICE_CACHE_ADDITIONAL_CURRENT_FIELDS = "gc_code|attribution_note"; + private static final String SERVICE_CACHE_ADDITIONAL_L3_FIELDS = "is_watched|my_notes"; + + private static final String METHOD_SEARCH_NEAREST = "services/caches/search/nearest"; + private static final String METHOD_SEARCH_BBOX = "services/caches/search/bbox"; + private static final String METHOD_RETRIEVE_CACHES = "services/caches/geocaches"; + + public static Geocache getCache(final String geoCode) { + final Parameters params = new Parameters("cache_code", geoCode); + final IConnector connector = ConnectorFactory.getConnector(geoCode); + if (!(connector instanceof OCApiConnector)) { + return null; + } - private static final String SERVICE_CACHE = "/okapi/services/caches/geocache"; - private static final String SERVICE_CACHE_FIELDS = "code|name|location|type|status|owner|founds|notfounds|size|difficulty|terrain|rating|rating_votes|recommendations|description|hint|images|latest_logs|date_hidden"; + final OCApiConnector ocapiConn = (OCApiConnector) connector; - private static final String SERVICE_NEAREST = "/okapi/services/caches/search/nearest"; + params.add("fields", getFullFields(ocapiConn)); + params.add("attribution_append", "none"); - public static Geocache getCache(final String geoCode) { - final Parameters params = new Parameters("cache_code", geoCode, "fields", SERVICE_CACHE_FIELDS); - final JSONObject data = request(ConnectorFactory.getConnector(geoCode), SERVICE_CACHE, params); + final JSONObject data = request(ocapiConn, OkapiService.SERVICE_CACHE, params); if (data == null) { return null; @@ -79,57 +140,144 @@ final public class OkapiClient { return parseCache(data); } - public static List<Geocache> getCachesAround(final Geopoint center, IConnector connector) { - String centerString = GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, center) + "|" + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, center); - final Parameters params = new Parameters("center", centerString); - final JSONObject data = request(connector, SERVICE_NEAREST, params); + public static List<Geocache> getCachesAround(final Geopoint center, final OCApiConnector connector) { + final String centerString = GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, center) + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, center); + final Parameters params = new Parameters("search_method", METHOD_SEARCH_NEAREST); + final Map<String, String> valueMap = new LinkedHashMap<String, String>(); + valueMap.put("center", centerString); + valueMap.put("limit", "20"); + + return requestCaches(connector, params, valueMap); + } + + private static List<Geocache> requestCaches(final OCApiConnector connector, final Parameters params, final Map<String, String> valueMap) { + addFilterParams(valueMap, connector); + params.add("search_params", new JSONObject(valueMap).toString()); + addRetrieveParams(params, connector); + + final JSONObject data = request(connector, OkapiService.SERVICE_SEARCH_AND_RETRIEVE, params); if (data == null) { - return null; + return Collections.emptyList(); } return parseCaches(data); } + // Assumes level 3 OAuth + public static List<Geocache> getCachesBBox(final Viewport viewport, final OCApiConnector connector) { + + if (viewport.getLatitudeSpan() == 0 || viewport.getLongitudeSpan() == 0) { + return Collections.emptyList(); + } + + final String bboxString = GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, viewport.bottomLeft) + + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, viewport.bottomLeft) + + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, viewport.topRight) + + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, viewport.topRight); + final Parameters params = new Parameters("search_method", METHOD_SEARCH_BBOX); + final Map<String, String> valueMap = new LinkedHashMap<String, String>(); + valueMap.put("bbox", bboxString); + + return requestCaches(connector, params, valueMap); + } + + public static boolean setWatchState(final Geocache cache, final boolean watched, final OCApiConnector connector) { + final Parameters params = new Parameters("cache_code", cache.getGeocode()); + params.add("watched", watched ? "true" : "false"); + + final JSONObject data = request(connector, OkapiService.SERVICE_MARK_CACHE, params); + + if (data == null) { + return false; + } + + cache.setOnWatchlist(watched); + + return true; + } + + public static LogResult postLog(final Geocache cache, final LogType logType, final Calendar date, final String log, final String logPassword, final OCApiConnector connector) { + final Parameters params = new Parameters("cache_code", cache.getGeocode()); + params.add("logtype", logType.oc_type); + params.add("comment", log); + params.add("comment_format", "plaintext"); + params.add("when", LOG_DATE_FORMAT.format(date.getTime())); + if (logType.equals(LogType.NEEDS_MAINTENANCE)) { + params.add("needs_maintenance", "true"); + } + if (logPassword != null) { + params.add("password", logPassword); + } + + final JSONObject data = request(connector, OkapiService.SERVICE_SUBMIT_LOG, params); + + if (data == null) { + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } + + try { + if (data.getBoolean("success")) { + return new LogResult(StatusCode.NO_ERROR, data.getString("log_uuid")); + } + + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } catch (final JSONException e) { + Log.e("OkapiClient.postLog", e); + } + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } + private static List<Geocache> parseCaches(final JSONObject response) { try { - final JSONArray cachesResponse = response.getJSONArray("results"); + // Check for empty result + final String result = response.getString("results"); + if (StringUtils.isBlank(result) || StringUtils.equals(result, "[]")) { + return Collections.emptyList(); + } + + // Get and iterate result list + final JSONObject cachesResponse = response.getJSONObject("results"); if (cachesResponse != null) { - ArrayList<String> geocodes = new ArrayList<String>(cachesResponse.length()); - for (int i = 0; i < cachesResponse.length(); i++) { - String geocode = cachesResponse.getString(i); - if (StringUtils.isNotBlank(geocode)) { - geocodes.add(geocode); - } - } - List<Geocache> caches = new ArrayList<Geocache>(geocodes.size()); - for (String geocode : geocodes) { - Geocache cache = getCache(geocode); + final List<Geocache> caches = new ArrayList<Geocache>(cachesResponse.length()); + @SuppressWarnings("unchecked") + final + Iterator<String> keys = cachesResponse.keys(); + while (keys.hasNext()) { + final String key = keys.next(); + final Geocache cache = parseSmallCache(cachesResponse.getJSONObject(key)); if (cache != null) { caches.add(cache); } } return caches; } - } catch (JSONException e) { - Log.e("OkapiClient.parseCaches", e); + } catch (final JSONException e) { + Log.e("OkapiClient.parseCachesResult", e); } - return null; + return Collections.emptyList(); + } + + private static Geocache parseSmallCache(final JSONObject response) { + final Geocache cache = new Geocache(); + cache.setReliableLatLon(true); + try { + + parseCoreCache(response, cache); + + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_CACHE)); + } catch (final JSONException e) { + Log.e("OkapiClient.parseSmallCache", e); + } + return cache; } private static Geocache parseCache(final JSONObject response) { final Geocache cache = new Geocache(); cache.setReliableLatLon(true); try { - cache.setGeocode(response.getString(CACHE_CODE)); - cache.setName(response.getString(CACHE_NAME)); - // not used: names - setLocation(cache, response.getString(CACHE_LOCATION)); - cache.setType(getCacheType(response.getString(CACHE_TYPE))); - final String status = response.getString(CACHE_STATUS); - cache.setDisabled(status.equalsIgnoreCase("Temporarily unavailable")); - cache.setArchived(status.equalsIgnoreCase("Archived")); + parseCoreCache(response, cache); // not used: url final JSONObject owner = response.getJSONObject(CACHE_OWNER); @@ -137,9 +285,7 @@ final public class OkapiClient { cache.getLogCounts().put(LogType.FOUND_IT, response.getInt(CACHE_FOUNDS)); cache.getLogCounts().put(LogType.DIDNT_FIND_IT, response.getInt(CACHE_NOTFOUNDS)); - cache.setSize(getCacheSize(response)); - cache.setDifficulty((float) response.getDouble(CACHE_DIFFICULTY)); - cache.setTerrain((float) response.getDouble(CACHE_TERRAIN)); + if (!response.isNull(CACHE_RATING)) { cache.setRating((float) response.getDouble(CACHE_RATING)); } @@ -147,14 +293,29 @@ final public class OkapiClient { cache.setFavoritePoints(response.getInt(CACHE_RECOMMENDATIONS)); // not used: req_password - cache.setDescription(response.getString(CACHE_DESCRIPTION)); - cache.setHint(Html.fromHtml(response.getString(CACHE_HINT)).toString()); + // Prepend gc-link to description if available + final StringBuilder description = new StringBuilder(500); + if (!response.isNull("gc_code")) { + final String gccode = response.getString("gc_code"); + description.append(cgeoapplication.getInstance().getResources() + .getString(R.string.cache_listed_on, GCConnector.getInstance().getName())) + .append(": <a href=\"http://coord.info/") + .append(gccode) + .append("\">") + .append(gccode) + .append("</a><br /><br />"); + } + description.append(response.getString(CACHE_DESCRIPTION)); + cache.setDescription(description.toString()); + + // currently the hint is delivered as HTML (contrary to OKAPI documentation), so we can store it directly + cache.setHint(response.getString(CACHE_HINT)); // not used: hints final JSONArray images = response.getJSONArray(CACHE_IMAGES); if (images != null) { for (int i = 0; i < images.length(); i++) { - JSONObject imageResponse = images.getJSONObject(i); + final JSONObject imageResponse = images.getJSONObject(i); if (imageResponse.getBoolean(CACHE_IMAGE_IS_SPOILER)) { final String title = imageResponse.getString(CACHE_IMAGE_CAPTION); final String url = absoluteUrl(imageResponse.getString(CACHE_IMAGE_URL), cache.getGeocode()); @@ -163,20 +324,50 @@ final public class OkapiClient { } } - // not used: attrnames + cache.setAttributes(parseAttributes(response.getJSONArray(CACHE_ATTRNAMES))); cache.setLogs(parseLogs(response.getJSONArray(CACHE_LATEST_LOGS))); cache.setHidden(parseDate(response.getString(CACHE_HIDDEN))); + //TODO: Store license per cache + //cache.setLicense(response.getString("attribution_note")); + cache.setWaypoints(parseWaypoints(response.getJSONArray(CACHE_WPTS)), false); + if (!response.isNull(CACHE_IS_WATCHED)) { + cache.setOnWatchlist(response.getBoolean(CACHE_IS_WATCHED)); + } + if (!response.isNull(CACHE_MY_NOTES)) { + cache.setPersonalNote(response.getString(CACHE_MY_NOTES)); + } + cache.setLogPasswordRequired(response.getBoolean(CACHE_REQ_PASSWORD)); cache.setDetailedUpdatedNow(); // save full detailed caches cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("OkapiClient.parseCache", e); } return cache; } - private static String absoluteUrl(String url, String geocode) { + private static void parseCoreCache(final JSONObject response, final Geocache cache) throws JSONException { + cache.setGeocode(response.getString(CACHE_CODE)); + cache.setName(response.getString(CACHE_NAME)); + // not used: names + setLocation(cache, response.getString(CACHE_LOCATION)); + cache.setType(getCacheType(response.getString(CACHE_TYPE))); + + final String status = response.getString(CACHE_STATUS); + cache.setDisabled(status.equalsIgnoreCase(CACHE_STATUS_DISABLED)); + cache.setArchived(status.equalsIgnoreCase(CACHE_STATUS_ARCHIVED)); + + cache.setSize(getCacheSize(response)); + cache.setDifficulty((float) response.getDouble(CACHE_DIFFICULTY)); + cache.setTerrain((float) response.getDouble(CACHE_TERRAIN)); + + if (!response.isNull(CACHE_IS_FOUND)) { + cache.setFound(response.getBoolean(CACHE_IS_FOUND)); + } + } + + private static String absoluteUrl(final String url, final String geocode) { final Uri uri = Uri.parse(url); if (!uri.isAbsolute()) { @@ -189,16 +380,16 @@ final public class OkapiClient { return url; } - private static String parseUser(JSONObject user) throws JSONException { + private static String parseUser(final JSONObject user) throws JSONException { return user.getString(USER_USERNAME); } - private static List<LogEntry> parseLogs(JSONArray logsJSON) { + private static List<LogEntry> parseLogs(final JSONArray logsJSON) { List<LogEntry> result = null; for (int i = 0; i < logsJSON.length(); i++) { try { - JSONObject logResponse = logsJSON.getJSONObject(i); - LogEntry log = new LogEntry( + final JSONObject logResponse = logsJSON.getJSONObject(i); + final LogEntry log = new LogEntry( parseUser(logResponse.getJSONObject(LOG_USER)), parseDate(logResponse.getString(LOG_DATE)).getTime(), parseLogType(logResponse.getString(LOG_TYPE)), @@ -207,14 +398,38 @@ final public class OkapiClient { result = new ArrayList<LogEntry>(); } result.add(log); - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("OkapiClient.parseLogs", e); } } return result; } - private static LogType parseLogType(String logType) { + private static List<Waypoint> parseWaypoints(final JSONArray wptsJson) { + List<Waypoint> result = null; + for (int i = 0; i < wptsJson.length(); i++) { + try { + final JSONObject wptResponse = wptsJson.getJSONObject(i); + final Waypoint wpt = new Waypoint(wptResponse.getString(WPT_NAME), + parseWptType(wptResponse.getString(WPT_TYPE)), + false); + wpt.setNote(wptResponse.getString(WPT_DESCRIPTION)); + final Geopoint pt = parseCoords(wptResponse.getString(WPT_LOCATION)); + if (pt != null) { + wpt.setCoords(pt); + } + if (result == null) { + result = new ArrayList<Waypoint>(); + } + result.add(wpt); + } catch (final JSONException e) { + Log.e("OkapiClient.parseWaypoints", e); + } + } + return result; + } + + private static LogType parseLogType(final String logType) { if ("Found it".equalsIgnoreCase(logType)) { return LogType.FOUND_IT; } @@ -224,20 +439,74 @@ final public class OkapiClient { return LogType.NOTE; } + private static WaypointType parseWptType(final String wptType) { + if ("parking".equalsIgnoreCase(wptType)) { + return WaypointType.PARKING; + } + if ("path".equalsIgnoreCase(wptType)) { + return WaypointType.TRAILHEAD; + } + if ("stage".equalsIgnoreCase(wptType)) { + return WaypointType.STAGE; + } + if ("physical-stage".equalsIgnoreCase(wptType)) { + return WaypointType.STAGE; + } + if ("virtual-stage".equalsIgnoreCase(wptType)) { + return WaypointType.PUZZLE; + } + if ("final".equalsIgnoreCase(wptType)) { + return WaypointType.FINAL; + } + if ("poi".equalsIgnoreCase(wptType)) { + return WaypointType.TRAILHEAD; + } + return WaypointType.WAYPOINT; + } + private static Date parseDate(final String date) { - final SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()); final String strippedDate = date.replaceAll("\\+0([0-9]){1}\\:00", "+0$100"); try { return ISO8601DATEFORMAT.parse(strippedDate); - } catch (ParseException e) { + } catch (final ParseException e) { Log.e("OkapiClient.parseDate", e); } return null; } + private static Geopoint parseCoords(final String location) { + final String latitude = StringUtils.substringBefore(location, SEPARATOR_STRING); + final String longitude = StringUtils.substringAfter(location, SEPARATOR_STRING); + if (StringUtils.isNotBlank(latitude) && StringUtils.isNotBlank(longitude)) { + return new Geopoint(latitude, longitude); + } + + return null; + } + + private static List<String> parseAttributes(final JSONArray nameList) { + + final List<String> result = new ArrayList<String>(); + + for (int i = 0; i < nameList.length(); i++) { + try { + final String name = nameList.getString(i); + final CacheAttribute attr = CacheAttribute.getByOcId(AttributeParser.getOcDeId(name)); + + if (attr != null) { + result.add(attr.rawName); + } + } catch (final JSONException e) { + Log.e("OkapiClient.parseAttributes", e); + } + } + + return result; + } + private static void setLocation(final Geocache cache, final String location) { - final String latitude = StringUtils.substringBefore(location, "|"); - final String longitude = StringUtils.substringAfter(location, "|"); + final String latitude = StringUtils.substringBefore(location, SEPARATOR_STRING); + final String longitude = StringUtils.substringAfter(location, SEPARATOR_STRING); cache.setCoords(new Geopoint(latitude, longitude)); } @@ -248,7 +517,7 @@ final public class OkapiClient { double size = 0; try { size = response.getDouble(CACHE_SIZE); - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("OkapiClient.getCacheSize", e); } switch ((int) Math.round(size)) { @@ -281,14 +550,57 @@ final public class OkapiClient { if (cacheType.equalsIgnoreCase("Virtual")) { return CacheType.VIRTUAL; } + if (cacheType.equalsIgnoreCase("Event")) { + return CacheType.EVENT; + } + if (cacheType.equalsIgnoreCase("Webcam")) { + return CacheType.WEBCAM; + } + if (cacheType.equalsIgnoreCase("Math/Physics")) { + return CacheType.MYSTERY; + } + if (cacheType.equalsIgnoreCase("Drive-In")) { + return CacheType.TRADITIONAL; + } return CacheType.UNKNOWN; } - private static JSONObject request(final IConnector connector, final String service, final Parameters params) { + private static String getCoreFields(final OCApiConnector connector) { if (connector == null) { - return null; + Log.e("OkapiClient.getCoreFields called with invalid connector"); + return StringUtils.EMPTY; } - if (!(connector instanceof OCApiConnector)) { + + if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + return SERVICE_CACHE_CORE_FIELDS + SEPARATOR + SERVICE_CACHE_CORE_L3_FIELDS; + } + + return SERVICE_CACHE_CORE_FIELDS; + } + + private static String getFullFields(final OCApiConnector connector) { + if (connector == null) { + Log.e("OkapiClient.getFullFields called with invalid connector"); + return StringUtils.EMPTY; + } + + final StringBuilder res = new StringBuilder(500); + + res.append(SERVICE_CACHE_CORE_FIELDS); + res.append(SEPARATOR).append(SERVICE_CACHE_ADDITIONAL_FIELDS); + if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + res.append(SEPARATOR).append(SERVICE_CACHE_CORE_L3_FIELDS); + res.append(SEPARATOR).append(SERVICE_CACHE_ADDITIONAL_L3_FIELDS); + } + if (connector.getApiSupport() == ApiSupport.current) { + res.append(SEPARATOR).append(SERVICE_CACHE_ADDITIONAL_CURRENT_FIELDS); + } + + return res.toString(); + } + + private static JSONObject request(final OCApiConnector connector, final OkapiService service, final Parameters params) { + if (connector == null) { return null; } @@ -297,10 +609,15 @@ final public class OkapiClient { return null; } - ((OCApiConnector) connector).addAuthentication(params); params.add("langpref", getPreferredLanguage()); - final String uri = "http://" + host + service; + if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + OAuth.signOAuth(host, service.methodName, "GET", false, params, Settings.getOCDETokenPublic(), Settings.getOCDETokenSecret(), connector.getCK(), connector.getCS()); + } else { + connector.addAuthentication(params); + } + + final String uri = "http://" + host + service.methodName; return Network.requestJSON(uri, params); } @@ -311,4 +628,79 @@ final public class OkapiClient { } return "en"; } + + private static void addFilterParams(final Map<String, String> valueMap, final OCApiConnector connector) { + if (!Settings.isExcludeDisabledCaches()) { + valueMap.put("status", "Available|Temporarily unavailable"); + } + if (Settings.isExcludeMyCaches() && connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + valueMap.put("exclude_my_own", "true"); + valueMap.put("found_status", "notfound_only"); + } + if (Settings.getCacheType() != CacheType.ALL) { + valueMap.put("type", getFilterFromType(Settings.getCacheType())); + } + } + + private static void addRetrieveParams(final Parameters params, final OCApiConnector connector) { + params.add("retr_method", METHOD_RETRIEVE_CACHES); + params.add("retr_params", "{\"fields\": \"" + getCoreFields(connector) + "\"}"); + params.add("wrap", "true"); + } + + private static String getFilterFromType(final CacheType cacheType) { + switch (cacheType) { + case EVENT: + return "Event"; + case MULTI: + return "Multi"; + case MYSTERY: + return "Quiz"; + case TRADITIONAL: + return "Traditional"; + case VIRTUAL: + return "Virtual"; + case WEBCAM: + return "Webcam"; + default: + return ""; + } + } + + public static UserInfo getUserInfo(final OCApiLiveConnector connector) { + final Parameters params = new Parameters("fields", USER_INFO_FIELDS); + + final JSONObject data = request(connector, OkapiService.SERVICE_USER, params); + + if (data == null) { + return new UserInfo(StringUtils.EMPTY, 0, UserInfoStatus.FAILED); + } + + String name = StringUtils.EMPTY; + boolean successUserName = false; + + if (!data.isNull(USER_USERNAME)) { + try { + name = data.getString(USER_USERNAME); + successUserName = true; + } catch (final JSONException e) { + Log.e("OkapiClient.getUserInfo - name", e); + } + } + + int finds = 0; + boolean successFinds = false; + + if (!data.isNull(USER_CACHES_FOUND)) { + try { + finds = data.getInt(USER_CACHES_FOUND); + successFinds = true; + } catch (final JSONException e) { + Log.e("OkapiClient.getUserInfo - finds", e); + } + } + + return new UserInfo(name, finds, successUserName && successFinds ? UserInfoStatus.SUCCESSFUL : UserInfoStatus.FAILED); + } + } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java b/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java new file mode 100644 index 0000000..22cb9dd --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java @@ -0,0 +1,70 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.LogCacheActivity; +import cgeo.geocaching.TrackableLog; +import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.ImageResult; +import cgeo.geocaching.connector.LogResult; +import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; + +import android.app.Activity; +import android.net.Uri; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +public class OkapiLoggingManager implements ILoggingManager { + + private final OCApiLiveConnector connector; + private final Geocache cache; + private LogCacheActivity activity; + + private final static List<LogType> standardLogTypes = Arrays.asList(LogType.FOUND_IT, LogType.DIDNT_FIND_IT, LogType.NOTE, LogType.NEEDS_MAINTENANCE); + private final static List<LogType> eventLogTypes = Arrays.asList(LogType.WILL_ATTEND, LogType.ATTENDED, LogType.NOTE); + + public OkapiLoggingManager(Activity activity, OCApiLiveConnector connector, Geocache cache) { + this.connector = connector; + this.cache = cache; + this.activity = (LogCacheActivity) activity; + } + + @Override + public void init() { + activity.onLoadFinished(); + } + + @Override + public LogResult postLog(Geocache cache, LogType logType, Calendar date, String log, String logPassword, List<TrackableLog> trackableLogs) { + final LogResult result = OkapiClient.postLog(cache, logType, date, log, logPassword, connector); + connector.login(null, null); + return result; + } + + @Override + public ImageResult postLogImage(String logId, String imageCaption, String imageDescription, Uri imageUri) { + return new ImageResult(StatusCode.LOG_POST_ERROR, ""); + } + + @Override + public boolean hasLoaderError() { + return false; + } + + @Override + public List<TrackableLog> getTrackables() { + return Collections.emptyList(); + } + + @Override + public List<LogType> getPossibleLogTypes() { + if (cache.isEventCache()) { + return eventLogTypes; + } + + return standardLogTypes; + } +} diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiService.java b/main/src/cgeo/geocaching/connector/oc/OkapiService.java new file mode 100644 index 0000000..ec09527 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OkapiService.java @@ -0,0 +1,21 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.connector.oc.OCApiConnector.OAuthLevel; + + +enum OkapiService { + SERVICE_CACHE("/okapi/services/caches/geocache", OAuthLevel.Level1), + SERVICE_SEARCH_AND_RETRIEVE("/okapi/services/caches/shortcuts/search_and_retrieve", OAuthLevel.Level1), + SERVICE_MARK_CACHE("/okapi/services/caches/mark", OAuthLevel.Level3), + SERVICE_SUBMIT_LOG("/okapi/services/logs/submit", OAuthLevel.Level3), + SERVICE_USER("/okapi/services/users/user", OAuthLevel.Level1); + + final String methodName; + final OAuthLevel level; + + OkapiService(final String methodName, final OAuthLevel level) { + this.methodName = methodName; + this.level = level; + } + +} diff --git a/main/src/cgeo/geocaching/connector/oc/UserInfo.java b/main/src/cgeo/geocaching/connector/oc/UserInfo.java new file mode 100644 index 0000000..0dc0440 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/UserInfo.java @@ -0,0 +1,41 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.R; + +public class UserInfo { + + public enum UserInfoStatus { + NOT_RETRIEVED(R.string.init_login_popup_working), + SUCCESSFUL(R.string.init_login_popup_ok), + FAILED(R.string.init_login_popup_failed), + NOT_SUPPORTED(R.string.init_login_popup_not_authorized); + + public final int resId; + + UserInfoStatus(int resId) { + this.resId = resId; + } + } + + private final String name; + private final int finds; + private final UserInfoStatus status; + + UserInfo(String name, int finds, UserInfoStatus status) { + this.name = name; + this.finds = finds; + this.status = status; + } + + public String getName() { + return name; + } + + public int getFinds() { + return finds; + } + + public UserInfoStatus getStatus() { + return status; + } +} diff --git a/main/src/cgeo/geocaching/connector/ox/OXConnector.java b/main/src/cgeo/geocaching/connector/ox/OXConnector.java index eec07e3..af33bb6 100644 --- a/main/src/cgeo/geocaching/connector/ox/OXConnector.java +++ b/main/src/cgeo/geocaching/connector/ox/OXConnector.java @@ -3,7 +3,7 @@ package cgeo.geocaching.connector.ox; import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.Settings; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByGeocode; diff --git a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java new file mode 100644 index 0000000..cd32d87 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java @@ -0,0 +1,10 @@ +package cgeo.geocaching.connector.trackable; + + +public abstract class AbstractTrackableConnector implements TrackableConnector { + + @Override + public boolean isLoggable() { + return false; + } +} diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java new file mode 100644 index 0000000..8387076 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java @@ -0,0 +1,42 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; +import cgeo.geocaching.network.Network; +import cgeo.geocaching.utils.Log; + +import java.util.regex.Pattern; + +public class GeokretyConnector extends AbstractTrackableConnector { + + private static final Pattern PATTERN_GK_CODE = Pattern.compile("GK[0-9A-F]{4,}"); + + @Override + public boolean canHandleTrackable(String geocode) { + return geocode != null && PATTERN_GK_CODE.matcher(geocode).matches(); + } + + @Override + public String getUrl(Trackable trackable) { + return "http://geokrety.org/konkret.php?id=" + getId(trackable.getGeocode()); + } + + @Override + public Trackable searchTrackable(String geocode, String guid, String id) { + final String page = Network.getResponseData(Network.getRequest("http://geokrety.org/export2.php?gkid=" + getId(geocode))); + if (page == null) { + return null; + } + return GeokretyParser.parse(page); + } + + private static int getId(String geocode) { + try { + final String hex = geocode.substring(2); + return Integer.parseInt(hex, 16); + } catch (final NumberFormatException e) { + Log.e("Trackable.getUrl", e); + } + return -1; + } + +} diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java new file mode 100644 index 0000000..66ca5f7 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java @@ -0,0 +1,82 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.R; +import cgeo.geocaching.Trackable; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.utils.Log; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import android.sax.Element; +import android.sax.EndTextElementListener; +import android.sax.RootElement; +import android.sax.StartElementListener; +import android.util.Xml; + +public class GeokretyParser { + + public static Trackable parse(final String page) { + final Trackable trackable = new Trackable(); + + final RootElement root = new RootElement("gkxml"); + final Element geokret = root.getChild("geokrety").getChild("geokret"); + + geokret.setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String name) { + trackable.setName(name); + } + }); + + geokret.setStartElementListener(new StartElementListener() { + + @Override + public void start(Attributes attributes) { + try { + if (attributes.getIndex("id") > -1) { + trackable.setGeocode(geocode(Integer.valueOf(attributes.getValue("id")))); + } + if (attributes.getIndex("dist") > -1) { + trackable.setDistance(Float.valueOf(attributes.getValue("dist"))); + } + if (attributes.getIndex("type") > -1) { + trackable.setType(getType(Integer.valueOf(attributes.getValue("type")))); + } + } catch (final NumberFormatException e) { + Log.e("Parsing geokret", e); + } + } + }); + + try { + Xml.parse(page, root.getContentHandler()); + return trackable; + } catch (final SAXException e) { + Log.w("Cannot parse geokrety", e); + } + + return null; + } + + protected static String getType(int type) { + switch (type) { + case 0: + return cgeoapplication.getInstance().getString(R.string.geokret_type_traditional); + case 1: + return cgeoapplication.getInstance().getString(R.string.geokret_type_book_or_media); + case 2: + return cgeoapplication.getInstance().getString(R.string.geokret_type_human); + case 3: + return cgeoapplication.getInstance().getString(R.string.geokret_type_coin); + case 4: + return cgeoapplication.getInstance().getString(R.string.geokret_type_post); + } + 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 new file mode 100644 index 0000000..c09dc07 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java @@ -0,0 +1,19 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; + +/** + * Methods to be implemented by any connector for handling trackables + * + */ +public interface TrackableConnector { + + public boolean canHandleTrackable(final String geocode); + + public String getUrl(final Trackable trackable); + + public boolean isLoggable(); + + public Trackable searchTrackable(String geocode, String guid, String id); + +} diff --git a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java new file mode 100644 index 0000000..0dac6cc --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java @@ -0,0 +1,50 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; +import cgeo.geocaching.connector.gc.GCParser; + +import java.util.regex.Pattern; + +public class TravelBugConnector extends AbstractTrackableConnector { + + /** + * TB codes really start with TB1, there is no padding or minimum length + */ + private final static Pattern PATTERN_TB_CODE = Pattern.compile("(TB[0-9A-Z]+)|([0-9A-Z]{6})", Pattern.CASE_INSENSITIVE); + + @Override + public boolean canHandleTrackable(String geocode) { + return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches(); + } + + @Override + public String getUrl(Trackable trackable) { + return "http://www.geocaching.com//track/details.aspx?tracker=" + trackable.getGeocode(); + } + + @Override + public boolean isLoggable() { + return true; + } + + @Override + public Trackable searchTrackable(String geocode, String guid, String id) { + return GCParser.searchTrackable(geocode, guid, id); + } + + /** + * initialization on demand holder pattern + */ + private static class Holder { + private static final TravelBugConnector INSTANCE = new TravelBugConnector(); + } + + private TravelBugConnector() { + // singleton + } + + public static TravelBugConnector getInstance() { + return Holder.INSTANCE; + } + +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java new file mode 100644 index 0000000..0295927 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java @@ -0,0 +1,24 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; + +import org.apache.commons.lang3.StringUtils; + +public class UnknownTrackableConnector extends AbstractTrackableConnector { + + @Override + public boolean canHandleTrackable(String geocode) { + return false; + } + + @Override + public String getUrl(Trackable trackable) { + return StringUtils.EMPTY; + } + + @Override + public Trackable searchTrackable(String geocode, String guid, String id) { + return null; + } + +} |
