diff options
Diffstat (limited to 'main/src/cgeo/geocaching/connector')
22 files changed, 1584 insertions, 259 deletions
diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index 112d6cf..9604b5f 100644 --- a/main/src/cgeo/geocaching/connector/AbstractConnector.java +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -1,8 +1,8 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.SearchResult; import cgeo.geocaching.cgCache; -import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.enumerations.CacheRealm; +import cgeo.geocaching.geopoint.Geopoint; import org.apache.commons.lang3.StringUtils; @@ -19,6 +19,31 @@ public abstract class AbstractConnector implements IConnector { } @Override + public boolean supportsOwnCoordinates() { + return false; + } + + /** + * Uploading modified coordinates to website + * + * @param cache + * @param wpt + * @return success + */ + @Override + public boolean uploadModifiedCoordinates(cgCache cache, Geopoint wpt) { + throw new UnsupportedOperationException(); + } + + /** + * {@link IConnector} + */ + @Override + public boolean deleteModifiedCoordinates(cgCache cache) { + throw new UnsupportedOperationException(); + } + + @Override public boolean supportsFavoritePoints() { return false; } @@ -38,11 +63,6 @@ public abstract class AbstractConnector implements IConnector { return false; } - @Override - public SearchResult searchByViewport(Viewport viewport, String tokens[]) { - return null; - } - protected static boolean isNumericId(final String string) { try { return Integer.parseInt(string) > 0; @@ -81,4 +101,20 @@ public abstract class AbstractConnector implements IConnector { } abstract protected String getCacheUrlPrefix(); + + /** + * {@link IConnector} + */ + @Override + public CacheRealm getCacheRealm() { + return CacheRealm.OTHER; + } + + /** + * {@link IConnector} + */ + @Override + public boolean isActivated() { + return false; + } } diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index bc4dcc0..4802c3e 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -3,25 +3,31 @@ package cgeo.geocaching.connector; import cgeo.geocaching.ICache; import cgeo.geocaching.SearchResult; import cgeo.geocaching.cgTrackable; +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.OCConnector; +import cgeo.geocaching.connector.oc.OCXMLApiConnector; import cgeo.geocaching.connector.ox.OXConnector; import cgeo.geocaching.geopoint.Viewport; import org.apache.commons.lang3.StringUtils; +import java.util.ArrayList; +import java.util.List; + public final class ConnectorFactory { private static final UnknownConnector UNKNOWN_CONNECTOR = new UnknownConnector(); private static final IConnector[] connectors = new IConnector[] { GCConnector.getInstance(), - new OCConnector("OpenCaching.DE", "www.opencaching.de", "OC"), + new OCXMLApiConnector("OpenCaching.DE", "www.opencaching.de", "OC"), new OCConnector("OpenCaching.CZ", "www.opencaching.cz", "OZ"), new OCApiConnector("OpenCaching.CO.UK", "www.opencaching.org.uk", "OK", "arU4okouc4GEjMniE2fq"), 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.no", "OS"), + 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"), @@ -31,10 +37,37 @@ public final class ConnectorFactory { UNKNOWN_CONNECTOR // the unknown connector MUST be the last one }; + private static final ISearchByViewPort[] searchByViewPortConns; + + private static final ISearchByCenter[] searchByCenterConns; + + static { + List<ISearchByViewPort> vpConns = new ArrayList<ISearchByViewPort>(); + for (IConnector conn : connectors) { + if (conn instanceof ISearchByViewPort) { + vpConns.add((ISearchByViewPort) conn); + } + } + searchByViewPortConns = vpConns.toArray(new ISearchByViewPort[] {}); + + List<ISearchByCenter> centerConns = new ArrayList<ISearchByCenter>(); + for (IConnector conn : connectors) { + // GCConnector is handled specially, omit it here! + if (conn instanceof ISearchByCenter && !(conn instanceof GCConnector)) { + centerConns.add((ISearchByCenter) conn); + } + } + searchByCenterConns = centerConns.toArray(new ISearchByCenter[] {}); + } + public static IConnector[] getConnectors() { return connectors; } + public static ISearchByCenter[] getSearchByCenterConnectors() { + return searchByCenterConns; + } + public static boolean canHandle(final String geocode) { if (isInvalidGeocode(geocode)) { return false; @@ -74,11 +107,19 @@ public final class ConnectorFactory { return StringUtils.isBlank(geocode) || !Character.isLetterOrDigit(geocode.charAt(0)); } - /** @see IConnector#searchByViewport */ + /** @see ISearchByViewPort#searchByViewport */ public static SearchResult searchByViewport(final Viewport viewport, final String[] tokens) { - // We have only connector capable of doing a 'searchByViewport()' - // If there is a second connector the information has to be collected from all collectors - return GCConnector.getInstance().searchByViewport(viewport, tokens); + + SearchResult result = new SearchResult(); + for (ISearchByViewPort vpconn : searchByViewPortConns) { + if (vpconn.isActivated()) { + SearchResult temp = vpconn.searchByViewport(viewport, tokens); + if (temp != null) { + result.addGeocodes(temp.getGeocodes()); + } + } + } + return result; } public static String getGeocodeFromURL(final String url) { diff --git a/main/src/cgeo/geocaching/connector/IConnector.java b/main/src/cgeo/geocaching/connector/IConnector.java index d6ac2ff..2944bed 100644 --- a/main/src/cgeo/geocaching/connector/IConnector.java +++ b/main/src/cgeo/geocaching/connector/IConnector.java @@ -1,8 +1,8 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.SearchResult; import cgeo.geocaching.cgCache; -import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.enumerations.CacheRealm; +import cgeo.geocaching.geopoint.Geopoint; public interface IConnector { /** @@ -72,15 +72,6 @@ public interface IConnector { public boolean supportsUserActions(); /** - * Search caches by viewport. - * - * @param viewport - * @param tokens - * @return - */ - public SearchResult searchByViewport(final Viewport viewport, final String[] tokens); - - /** * return true if this is a ZIP file containing a GPX file * * @param fileName @@ -106,10 +97,48 @@ public interface IConnector { /** * extract a geocode from the given URL, if this connector can handle that URL somehow - * + * * @param url * @return */ public String getGeocodeFromUrl(final String url); + /** + * enable/disable uploading modified coordinates to website + * + * @return true, when uploading is possible + */ + public boolean supportsOwnCoordinates(); + + /** + * Uploading modified coordinates to website + * + * @param cache + * @param wpt + * @return success + */ + public boolean uploadModifiedCoordinates(cgCache cache, Geopoint wpt); + + /** + * Reseting of modified coordinates on website to details + * + * @param cache + * @return success + */ + public boolean deleteModifiedCoordinates(cgCache cache); + + /** + * The CacheRealm this cache belongs to + * + * @return + */ + public CacheRealm getCacheRealm(); + + /** + * Return true if this connector is activated for online + * interaction (download details, do searches, ...) + * + * @return + */ + public boolean isActivated(); } diff --git a/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java b/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java index 62645c2..3fdd61f 100644 --- a/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java +++ b/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java @@ -10,4 +10,5 @@ import cgeo.geocaching.geopoint.Geopoint; public interface ISearchByCenter { public SearchResult searchByCenter(final Geopoint center); + public boolean isActivated(); } diff --git a/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java b/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java index 316cf00..f1bd2ce 100644 --- a/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java +++ b/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java @@ -5,4 +5,6 @@ import cgeo.geocaching.geopoint.Viewport; public interface ISearchByViewPort { public SearchResult searchByViewport(final Viewport viewport, final String[] tokens); + + public boolean isActivated(); } diff --git a/main/src/cgeo/geocaching/connector/gc/AbstractSearchThread.java b/main/src/cgeo/geocaching/connector/gc/AbstractSearchThread.java index d0e45f6..f19064d 100644 --- a/main/src/cgeo/geocaching/connector/gc/AbstractSearchThread.java +++ b/main/src/cgeo/geocaching/connector/gc/AbstractSearchThread.java @@ -12,7 +12,7 @@ abstract public class AbstractSearchThread extends Thread { private final Handler handler; private static AbstractSearchThread currentInstance; - public AbstractSearchThread(final Handler handler) { + protected AbstractSearchThread(final Handler handler) { this.handler = handler; } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index 197d4e6..8943835 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -3,10 +3,12 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.cgData; import cgeo.geocaching.connector.AbstractConnector; 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; @@ -18,21 +20,24 @@ import org.apache.commons.lang3.StringUtils; import java.util.regex.Pattern; -public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter { +public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort { private static final String HTTP_COORD_INFO = "http://coord.info/"; - private static GCConnector instance; private static final Pattern gpxZipFilePattern = Pattern.compile("\\d{7,}(_.+)?\\.zip", Pattern.CASE_INSENSITIVE); private GCConnector() { // singleton } + /** + * initialization on demand holder pattern + */ + private static class Holder { + private static final GCConnector INSTANCE = new GCConnector(); + } + public static GCConnector getInstance() { - if (instance == null) { - instance = new GCConnector(); - } - return instance; + return Holder.INSTANCE; } @Override @@ -50,6 +55,11 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override + public boolean supportsOwnCoordinates() { + return true; + } + + @Override public boolean supportsWatchList() { return true; } @@ -83,11 +93,10 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, if (StringUtils.isEmpty(page)) { final SearchResult search = new SearchResult(); - if (cgeoapplication.getInstance().isThere(geocode, guid, true, false)) { + if (cgData.isThere(geocode, guid, true, false)) { if (StringUtils.isBlank(geocode) && StringUtils.isNotBlank(guid)) { Log.i("Loading old cache from cache."); - - search.addGeocode(cgeoapplication.getInstance().getGeocode(guid)); + search.addGeocode(cgData.getGeocodeForGuid(guid)); } else { search.addGeocode(geocode); } @@ -103,7 +112,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, final SearchResult searchResult = GCParser.parseCache(page, handler); if (searchResult == null || CollectionUtils.isEmpty(searchResult.getGeocodes())) { - Log.e("GCConnector.searchByGeocode: No cache parsed"); + Log.w("GCConnector.searchByGeocode: No cache parsed"); return searchResult; } @@ -129,7 +138,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, public static boolean addToWatchlist(cgCache cache) { final boolean added = GCParser.addToWatchlist(cache); if (added) { - cgeoapplication.getInstance().updateCache(cache); + cgData.saveChangedCache(cache); } return added; } @@ -137,28 +146,64 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, public static boolean removeFromWatchlist(cgCache cache) { final boolean removed = GCParser.removeFromWatchlist(cache); if (removed) { - cgeoapplication.getInstance().updateCache(cache); + cgData.saveChangedCache(cache); } return removed; } + /** + * Add a cache to the favorites list. + * + * This must not be called from the UI thread. + * + * @param cache the cache to add + * @return <code>true</code> if the cache was sucessfully added, <code>false</code> otherwise + */ + public static boolean addToFavorites(cgCache cache) { final boolean added = GCParser.addToFavorites(cache); if (added) { - cgeoapplication.getInstance().updateCache(cache); + cgData.saveChangedCache(cache); } return added; } + /** + * Remove a cache from the favorites list. + * + * This must not be called from the UI thread. + * + * @param cache the cache to add + * @return <code>true</code> if the cache was sucessfully added, <code>false</code> otherwise + */ + public static boolean removeFromFavorites(cgCache cache) { final boolean removed = GCParser.removeFromFavorites(cache); if (removed) { - cgeoapplication.getInstance().updateCache(cache); + cgData.saveChangedCache(cache); } return removed; } @Override + public boolean uploadModifiedCoordinates(cgCache cache, Geopoint wpt) { + final boolean uploaded = GCParser.uploadModifiedCoordinates(cache, wpt); + if (uploaded) { + cgData.saveChangedCache(cache); + } + return uploaded; + } + + @Override + public boolean deleteModifiedCoordinates(cgCache cache) { + final boolean deleted = GCParser.deleteModifiedCoordinates(cache); + if (deleted) { + cgData.saveChangedCache(cache); + } + return deleted; + } + + @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; @@ -173,4 +218,14 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, protected String getCacheUrlPrefix() { return HTTP_COORD_INFO; } + + @Override + public CacheRealm getCacheRealm() { + return CacheRealm.GC; + } + + @Override + public boolean isActivated() { + return true; + } } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index 2b4ca46..282c88c 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -1,5 +1,6 @@ package cgeo.geocaching.connector.gc; +import java.util.Locale; import java.util.regex.Pattern; /** @@ -49,7 +50,7 @@ public final class GCConstants { public final static Pattern PATTERN_FAVORITE = Pattern.compile("<div id=\"pnlFavoriteCache\">"); // without 'class="hideMe"' inside the tag ! public final static Pattern PATTERN_FAVORITECOUNT = Pattern.compile("<a id=\"uxFavContainerLink\"[^>]+>[^<]*<div[^<]*<span class=\"favorite-value\">\\D*([0-9]+?)</span>"); public final static Pattern PATTERN_COUNTLOGS = Pattern.compile("<span id=\"ctl00_ContentBody_lblFindCounts\"><p(.+?)</p></span>"); - public final static Pattern PATTERN_LOGBOOK = Pattern.compile("initalLogs = (\\{.+\\});"); + public final static Pattern PATTERN_LOGBOOK = Pattern.compile("initalLogs = (\\{.+\\});"); // The "inital" typo really comes from gc.com site /** Two groups ! */ public final static Pattern PATTERN_COUNTLOG = Pattern.compile("<img src=\"/images/logtypes/([0-9]+)\\.png\"[^>]+> (\\d*[,.]?\\d+)"); public static final Pattern PATTERN_PREMIUMMEMBERS = Pattern.compile("<p class=\"Warning NoBottomSpacing\">This is a Premium Member Only cache.</p>"); @@ -103,7 +104,7 @@ public final class GCConstants { public final static Pattern PATTERN_TRACKABLE_ICON = Pattern.compile("<img id=\"ctl00_ContentBody_BugTypeImage\" class=\"TravelBugHeaderIcon\" src=\"([^\"]+)\"[^>]*>"); public final static Pattern PATTERN_TRACKABLE_TYPE = Pattern.compile("<img id=\"ctl00_ContentBody_BugTypeImage\" class=\"TravelBugHeaderIcon\" src=\"[^\"]+\" alt=\"([^\"]+)\"[^>]*>"); public final static Pattern PATTERN_TRACKABLE_DISTANCE = Pattern.compile("<h4[^>]*\\W*Tracking History \\(([0-9.,]+(km|mi))[^\\)]*\\)"); - public final static Pattern PATTERN_TRACKABLE_LOG = Pattern.compile("<tr class=\"Data BorderTop .+?src=\"/images/logtypes/([^.]+)\\.png[^>]+> ([^<]+)</td>.+?guid.+?>([^<]+)</a>.+?(?:guid=([^\"]+)\">(<span[^>]+>)?([^<]+)</.+?)?<td colspan=\"4\">(.+?)(?:<ul.+?ul>)?\\s*</td>\\s*</tr>"); + public final static Pattern PATTERN_TRACKABLE_LOG = Pattern.compile("<tr class=\"Data BorderTop .+?src=\"/images/logtypes/([^.]+)\\.png[^>]+> ([^<]+)</td>.+?guid.+?>([^<]+)</a>.+?(?:guid=([^\"]+)\">(<span[^>]+>)?([^<]+)</.+?)?<td colspan=\"4\">\\s*<div>(.*?)</div>\\s*(?:<ul.+?ul>)?\\s*</td>\\s*</tr>"); public final static Pattern PATTERN_TRACKABLE_LOG_IMAGES = Pattern.compile("<li><a href=\"([^\"]+)\".+?LogImgTitle.+?>([^<]+)</"); /** @@ -164,8 +165,9 @@ public final class GCConstants { public final static String STRING_PREMIUMONLY_2 = "Sorry, the owner of this listing has made it viewable to Premium Members only."; public final static String STRING_PREMIUMONLY_1 = "has chosen to make this cache listing visible to Premium Members only."; - public final static String STRING_UNPUBLISHED_OWNER = "Cache is Unpublished"; + public final static String STRING_UNPUBLISHED_OWNER = "cache has not been published yet"; public final static String STRING_UNPUBLISHED_OTHER = "you cannot view this cache listing until it has been published"; + public final static String STRING_UNPUBLISHED_FROM_SEARCH = "UnpublishedCacheSearchWidget"; public final static String STRING_UNKNOWN_ERROR = "An Error Has Occurred"; public final static String STRING_DISABLED = "<li>This cache is temporarily unavailable."; public final static String STRING_ARCHIVED = "<li>This cache has been archived,"; @@ -185,14 +187,14 @@ public final class GCConstants { * see http://support.groundspeak.com/index.php?pg=kb.printer.friendly&id=1#p221 */ public static long gccodeToGCId(final String gccode) { - long gcid = 0; long base = GC_BASE31; - String geocodeWO = gccode.substring(2).toUpperCase(); + 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; } + long gcid = 0; for (int p = 0; p < geocodeWO.length(); p++) { gcid = base * gcid + SEQUENCE_GCID.indexOf(geocodeWO.charAt(p)); } diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index 681a1d7..9a8123d 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -3,6 +3,7 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Settings; import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; @@ -40,10 +41,10 @@ public class GCMap { final SearchResult result = new SearchResult(); final String geocodeList = StringUtils.join(geocodes.toArray(), "|"); - final String referer = GCConstants.URL_LIVE_MAP_DETAILS; try { final Parameters params = new Parameters("i", geocodeList, "_", String.valueOf(System.currentTimeMillis())); + final String referer = GCConstants.URL_LIVE_MAP_DETAILS; final String data = StringUtils.defaultString(Tile.requestMapInfo(referer, params, referer)); // Example JSON information @@ -166,6 +167,8 @@ public class GCMap { // iterate over the data and construct all caches in this tile Map<String, List<UTFGridPosition>> positions = new HashMap<String, List<UTFGridPosition>>(); // JSON id as key + Map<String, List<UTFGridPosition>> singlePositions = new HashMap<String, List<UTFGridPosition>>(); // JSON id as key + for (int i = 1; i < keys.length(); i++) { // index 0 is empty String key = keys.getString(i); if (StringUtils.isNotBlank(key)) { @@ -177,12 +180,20 @@ public class GCMap { nameCache.put(id, cacheInfo.getString("n")); List<UTFGridPosition> listOfPositions = positions.get(id); + List<UTFGridPosition> singleListOfPositions = singlePositions.get(id); + if (listOfPositions == null) { listOfPositions = new ArrayList<UTFGridPosition>(); positions.put(id, listOfPositions); + singleListOfPositions = new ArrayList<UTFGridPosition>(); + singlePositions.put(id, singleListOfPositions); } listOfPositions.add(pos); + if (dataForKey.length() == 1) { + singleListOfPositions.add(pos); + } + } } } @@ -198,12 +209,16 @@ public class GCMap { cache.setName(nameCache.get(id)); cache.setZoomlevel(tile.getZoomlevel()); cache.setCoords(tile.getCoord(xy)); - if (strategy.flags.contains(StrategyFlag.PARSE_TILES) && positions.size() < 64 && bitmap != null) { - // don't parse if there are too many caches. The decoding would return too much wrong results - IconDecoder.parseMapPNG(cache, bitmap, xy, tile.getZoomlevel()); + if (strategy.flags.contains(StrategyFlag.PARSE_TILES) && bitmap != null) { + for (UTFGridPosition singlePos : singlePositions.get(id)) { + if (IconDecoder.parseMapPNG(cache, bitmap, singlePos, tile.getZoomlevel())) { + break; // cache parsed + } + } } else { cache.setType(CacheType.UNKNOWN); } + boolean exclude = false; if (Settings.isExcludeMyCaches() && (cache.isFound() || cache.isOwn())) { // workaround for BM exclude = true; @@ -313,7 +328,7 @@ public class GCMap { String data = Tile.requestMapInfo(GCConstants.URL_MAP_INFO, params, GCConstants.URL_LIVE_MAP); if (StringUtils.isEmpty(data)) { - Log.e("GCBase.searchByViewport: No data from server for tile (" + tile.getX() + "/" + tile.getY() + ")"); + Log.w("GCBase.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())) { @@ -341,7 +356,7 @@ public class GCMap { if (search != null && !search.isEmpty()) { final Set<String> geocodes = search.getGeocodes(); if (Settings.isPremiumMember()) { - lastSearchViewport = cgeoapplication.getInstance().getBounds(geocodes); + lastSearchViewport = cgData.getBounds(geocodes); } else { lastSearchViewport = new Viewport(center, center); } diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 9edc34f..91b0ddd 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -6,6 +6,7 @@ import cgeo.geocaching.SearchResult; import cgeo.geocaching.Settings; import cgeo.geocaching.TrackableLog; import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgData; import cgeo.geocaching.cgImage; import cgeo.geocaching.cgTrackable; import cgeo.geocaching.cgWaypoint; @@ -30,6 +31,7 @@ import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.LazyInitializedList; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.MatcherWrapper; import ch.boye.httpclientandroidlib.HttpResponse; @@ -43,12 +45,7 @@ import org.json.JSONObject; import android.net.Uri; import android.text.Html; -import android.text.Spannable; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.text.style.StrikethroughSpan; -import java.net.URLDecoder; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -58,7 +55,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Locale; import java.util.Set; -import java.util.regex.Matcher; public abstract class GCParser { private final static SimpleDateFormat dateTbIn1 = new SimpleDateFormat("EEEEE, dd MMMMM yyyy", Locale.ENGLISH); // Saturday, 28 March 2009 @@ -71,8 +67,6 @@ public abstract class GCParser { } final List<String> cids = new ArrayList<String>(); - String recaptchaChallenge = null; - String recaptchaText = null; String page = pageContent; final SearchResult searchResult = new SearchResult(); @@ -81,6 +75,7 @@ public abstract class GCParser { // recaptcha AbstractSearchThread thread = AbstractSearchThread.getCurrentInstance(); + String recaptchaChallenge = null; if (showCaptcha) { String recaptchaJsParam = BaseUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null); @@ -133,7 +128,7 @@ public abstract class GCParser { } try { - final Matcher matcherGuidAndDisabled = GCConstants.PATTERN_SEARCH_GUIDANDDISABLED.matcher(row); + final MatcherWrapper matcherGuidAndDisabled = new MatcherWrapper(GCConstants.PATTERN_SEARCH_GUIDANDDISABLED, row); while (matcherGuidAndDisabled.find()) { if (matcherGuidAndDisabled.groupCount() > 0) { @@ -163,29 +158,32 @@ public abstract class GCParser { continue; } - String inventoryPre = null; - - cache.setGeocode(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_GEOCODE, true, 1, cache.getGeocode(), true).toUpperCase()); + cache.setGeocode(BaseUtils.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 direction - image if (Settings.getLoadDirImg()) { - cache.setDirectionImg(URLDecoder.decode(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION, true, 1, cache.getDirectionImg(), true))); + cache.setDirectionImg(Network.decode(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION, true, 1, cache.getDirectionImg(), true))); } // cache inventory - final Matcher matcherTbs = GCConstants.PATTERN_SEARCH_TRACKABLES.matcher(row); + final MatcherWrapper matcherTbs = new MatcherWrapper(GCConstants.PATTERN_SEARCH_TRACKABLES, row); + String inventoryPre = null; while (matcherTbs.find()) { if (matcherTbs.groupCount() > 0) { - cache.setInventoryItems(Integer.parseInt(matcherTbs.group(1))); + try { + cache.setInventoryItems(Integer.parseInt(matcherTbs.group(1))); + } catch (NumberFormatException e) { + Log.e("Error parsing trackables count", e); + } inventoryPre = matcherTbs.group(2); } } if (StringUtils.isNotBlank(inventoryPre)) { - final Matcher matcherTbsInside = GCConstants.PATTERN_SEARCH_TRACKABLESINSIDE.matcher(inventoryPre); + final MatcherWrapper matcherTbsInside = new MatcherWrapper(GCConstants.PATTERN_SEARCH_TRACKABLESINSIDE, inventoryPre); while (matcherTbsInside.find()) { if (matcherTbsInside.groupCount() == 2 && matcherTbsInside.group(2) != null && @@ -222,16 +220,6 @@ public abstract class GCParser { Log.w("GCParser.parseSearch: Failed to parse favourite count"); } - if (cache.getNameSp() == null) { - cache.setNameSp((new Spannable.Factory()).newSpannable(cache.getName())); - if (cache.isDisabled() || cache.isArchived()) { // strike - cache.getNameSp().setSpan(new StrikethroughSpan(), 0, cache.getNameSp().toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (cache.isArchived()) { - cache.getNameSp().setSpan(new ForegroundColorSpan(cgeoapplication.getInstance().getResources().getColor(R.color.archived_cache_color)), 0, cache.getNameSp().toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - searchResult.addCache(cache); } @@ -245,6 +233,7 @@ public abstract class GCParser { Log.w("GCParser.parseSearch: Failed to parse cache count"); } + String recaptchaText = null; if (thread != null && recaptchaChallenge != null) { if (thread.getText() == null) { thread.waitForUser(); @@ -253,7 +242,7 @@ public abstract class GCParser { recaptchaText = thread.getText(); } - if (cids.size() > 0 && (Settings.isPremiumMember() || showCaptcha) && (recaptchaChallenge == null || StringUtils.isNotBlank(recaptchaText))) { + if (!cids.isEmpty() && (Settings.isPremiumMember() || showCaptcha) && (recaptchaChallenge == null || StringUtils.isNotBlank(recaptchaText))) { Log.i("Trying to get .loc for " + cids.size() + " caches"); try { @@ -326,7 +315,7 @@ public abstract class GCParser { // save full detailed caches CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_cache); - cgeoapplication.getInstance().saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); // update progress message so user knows we're still working. This is more of a place holder than // actual indication of what the program is doing @@ -345,7 +334,7 @@ public abstract class GCParser { final SearchResult searchResult = new SearchResult(); - if (page.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || page.contains(GCConstants.STRING_UNPUBLISHED_OWNER)) { + if (page.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || page.contains(GCConstants.STRING_UNPUBLISHED_OWNER) || page.contains(GCConstants.STRING_UNPUBLISHED_FROM_SEARCH)) { searchResult.setError(StatusCode.UNPUBLISHED_CACHE); return searchResult; } @@ -383,10 +372,12 @@ public abstract class GCParser { cache.setName(cacheName); // owner real name - cache.setOwnerUserId(URLDecoder.decode(BaseUtils.getMatch(page, GCConstants.PATTERN_OWNER_USERID, true, cache.getOwnerUserId()))); + cache.setOwnerUserId(Network.decode(BaseUtils.getMatch(page, GCConstants.PATTERN_OWNER_USERID, true, cache.getOwnerUserId()))); cache.setOwn(StringUtils.equalsIgnoreCase(cache.getOwnerUserId(), Settings.getUsername())); + cache.setUserModifiedCoords(false); + String tableInside = page; int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS); @@ -401,13 +392,21 @@ public abstract class GCParser { // cache terrain String result = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_TERRAIN, true, null); if (result != null) { - cache.setTerrain(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); + try { + cache.setTerrain(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); + } catch (NumberFormatException e) { + Log.e("Error parsing terrain value", e); + } } // cache difficulty result = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_DIFFICULTY, true, null); if (result != null) { - cache.setDifficulty(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); + try { + cache.setDifficulty(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); + } catch (NumberFormatException e) { + Log.e("Error parsing difficulty value", e); + } } // owner @@ -432,10 +431,14 @@ public abstract class GCParser { } // favourite - cache.setFavoritePoints(Integer.parseInt(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_FAVORITECOUNT, true, "0"))); + try { + cache.setFavoritePoints(Integer.parseInt(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_FAVORITECOUNT, true, "0"))); + } catch (NumberFormatException e) { + Log.e("Error parsing favourite count", e); + } // cache size - cache.setSize(CacheSize.getById(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_SIZE, true, CacheSize.NOT_CHOSEN.id).toLowerCase())); + cache.setSize(CacheSize.getById(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_SIZE, true, CacheSize.NOT_CHOSEN.id))); } // cache found @@ -497,21 +500,21 @@ public abstract class GCParser { try { final String attributesPre = BaseUtils.getMatch(page, GCConstants.PATTERN_ATTRIBUTES, true, null); if (null != attributesPre) { - final Matcher matcherAttributesInside = GCConstants.PATTERN_ATTRIBUTESINSIDE.matcher(attributesPre); + final MatcherWrapper matcherAttributesInside = new MatcherWrapper(GCConstants.PATTERN_ATTRIBUTESINSIDE, attributesPre); final ArrayList<String> attributes = new ArrayList<String>(); while (matcherAttributesInside.find()) { if (matcherAttributesInside.groupCount() > 1 && !matcherAttributesInside.group(2).equalsIgnoreCase("blank")) { // by default, use the tooltip of the attribute - String attribute = matcherAttributesInside.group(2).toLowerCase(); + String attribute = matcherAttributesInside.group(2).toLowerCase(Locale.US); // if the image name can be recognized, use the image name as attribute - String imageName = matcherAttributesInside.group(1).trim(); - if (imageName.length() > 0) { + final String imageName = matcherAttributesInside.group(1).trim(); + if (StringUtils.isNotEmpty(imageName)) { int start = imageName.lastIndexOf('/'); int end = imageName.lastIndexOf('.'); if (start >= 0 && end >= 0) { - attribute = imageName.substring(start + 1, end).replace('-', '_').toLowerCase(); + attribute = imageName.substring(start + 1, end).replace('-', '_').toLowerCase(Locale.US); } } attributes.add(attribute); @@ -531,24 +534,20 @@ public abstract class GCParser { } CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_spoilers); - final Matcher matcherSpoilersInside = GCConstants.PATTERN_SPOILER_IMAGE.matcher(page); + final MatcherWrapper matcherSpoilersInside = new MatcherWrapper(GCConstants.PATTERN_SPOILER_IMAGE, page); 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 - // - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - String url = new String(matcherSpoilersInside.group(1).replace("/display", "")); + String url = matcherSpoilersInside.group(1).replace("/display", ""); String title = null; if (matcherSpoilersInside.group(3) != null) { - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - title = new String(matcherSpoilersInside.group(3)); + title = matcherSpoilersInside.group(3); } String description = null; if (matcherSpoilersInside.group(4) != null) { - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - description = new String(matcherSpoilersInside.group(4)); + description = matcherSpoilersInside.group(4); } cache.addSpoiler(new cgImage(url, title, description)); } @@ -561,7 +560,7 @@ public abstract class GCParser { try { cache.setInventoryItems(0); - final Matcher matcherInventory = GCConstants.PATTERN_INVENTORY.matcher(page); + final MatcherWrapper matcherInventory = new MatcherWrapper(GCConstants.PATTERN_INVENTORY, page); if (matcherInventory.find()) { if (cache.getInventory() == null) { cache.setInventory(new ArrayList<cgTrackable>()); @@ -571,15 +570,13 @@ public abstract class GCParser { final String inventoryPre = matcherInventory.group(2); if (StringUtils.isNotBlank(inventoryPre)) { - final Matcher matcherInventoryInside = GCConstants.PATTERN_INVENTORYINSIDE.matcher(inventoryPre); + final MatcherWrapper matcherInventoryInside = new MatcherWrapper(GCConstants.PATTERN_INVENTORYINSIDE, inventoryPre); while (matcherInventoryInside.find()) { if (matcherInventoryInside.groupCount() > 0) { final cgTrackable inventoryItem = new cgTrackable(); - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - inventoryItem.setGuid(new String(matcherInventoryInside.group(1))); - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - inventoryItem.setName(new String(matcherInventoryInside.group(2))); + inventoryItem.setGuid(matcherInventoryInside.group(1)); + inventoryItem.setName(matcherInventoryInside.group(2)); cache.getInventory().add(inventoryItem); cache.setInventoryItems(cache.getInventoryItems() + 1); @@ -594,14 +591,12 @@ public abstract class GCParser { } // cache logs counts - try - { + try { final String countlogs = BaseUtils.getMatch(page, GCConstants.PATTERN_COUNTLOGS, true, null); if (null != countlogs) { - final Matcher matcherLog = GCConstants.PATTERN_COUNTLOG.matcher(countlogs); + final MatcherWrapper matcherLog = new MatcherWrapper(GCConstants.PATTERN_COUNTLOG, countlogs); - while (matcherLog.find()) - { + while (matcherLog.find()) { String typeStr = matcherLog.group(1); String countStr = matcherLog.group(2).replaceAll("[.,]", ""); @@ -612,8 +607,7 @@ public abstract class GCParser { } } } - } catch (Exception e) - { + } catch (Exception e) { // failed to parse logs Log.w("GCParser.parseCache: Failed to parse cache log count"); } @@ -626,7 +620,7 @@ public abstract class GCParser { final String originalCoords = BaseUtils.getMatch(page, GCConstants.PATTERN_LATLON_ORIG, false, null); if (null != originalCoords) { - final cgWaypoint waypoint = new cgWaypoint(cgeoapplication.getInstance().getString(R.string.cache_coordinates_original), WaypointType.WAYPOINT, false); + final cgWaypoint waypoint = new cgWaypoint(cgeoapplication.getInstance().getString(R.string.cache_coordinates_original), WaypointType.ORIGINAL, false); waypoint.setCoords(new Geopoint(originalCoords)); cache.addOrChangeWaypoint(waypoint, false); cache.setUserModifiedCoords(true); @@ -634,10 +628,7 @@ public abstract class GCParser { } catch (Geopoint.GeopointException e) { } - int wpBegin; - int wpEnd; - - wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">"); + int wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">"); if (wpBegin != -1) { // parse waypoints if (CancellableHandler.isCancelled(handler)) { return null; @@ -646,7 +637,7 @@ public abstract class GCParser { String wpList = page.substring(wpBegin); - wpEnd = wpList.indexOf("</p>"); + int wpEnd = wpList.indexOf("</p>"); if (wpEnd > -1 && wpEnd <= wpList.length()) { wpList = wpList.substring(0, wpEnd); } @@ -663,9 +654,8 @@ public abstract class GCParser { final String[] wpItems = wpList.split("<tr"); - String[] wp; for (int j = 1; j < wpItems.length; j++) { - wp = wpItems[j].split("<td"); + String[] wp = wpItems[j].split("<td"); // waypoint name // res is null during the unit tests @@ -756,7 +746,7 @@ public abstract class GCParser { final SearchResult searchResult = parseSearch(url, page, showCaptcha); if (searchResult == null || CollectionUtils.isEmpty(searchResult.getGeocodes())) { - Log.e("GCParser.searchByNextPage: No cache parsed"); + Log.w("GCParser.searchByNextPage: No cache parsed"); return search; } @@ -923,7 +913,7 @@ public abstract class GCParser { trackable = parseTrackable(page, geocode); if (trackable == null) { - Log.e("GCParser.searchTrackable: No trackable parsed"); + Log.w("GCParser.searchTrackable: No trackable parsed"); return null; } @@ -1004,7 +994,7 @@ public abstract class GCParser { // maintenance, archived needs to be confirmed - final Matcher matcher = GCConstants.PATTERN_MAINTENANCE.matcher(page); + final MatcherWrapper matcher = new MatcherWrapper(GCConstants.PATTERN_MAINTENANCE, page); try { if (matcher.find() && matcher.groupCount() > 0) { @@ -1053,12 +1043,12 @@ public abstract class GCParser { try { - final Matcher matcherOk = GCConstants.PATTERN_OK1.matcher(page); + final MatcherWrapper matcherOk = new MatcherWrapper(GCConstants.PATTERN_OK1, page); if (matcherOk.find()) { Log.i("Log successfully posted to cache #" + cacheid); if (geocode != null) { - cgeoapplication.getInstance().saveVisitDate(geocode); + cgData.saveVisitDate(geocode); } Login.getLoginStatus(page); @@ -1122,7 +1112,7 @@ public abstract class GCParser { try { - final Matcher matcherOk = GCConstants.PATTERN_OK2.matcher(page); + final MatcherWrapper matcherOk = new MatcherWrapper(GCConstants.PATTERN_OK2, page); if (matcherOk.find()) { Log.i("Log successfully posted to trackable #" + trackingCode); return StatusCode.NO_ERROR; @@ -1211,6 +1201,8 @@ public abstract class GCParser { /** * Adds the cache to the favorites of the user. * + * This must not be called from the UI thread. + * * @param cache * the cache to add * @return <code>false</code> if an error occurred, <code>true</code> otherwise @@ -1241,7 +1233,9 @@ public abstract class GCParser { } /** - * Removes the cache from the Favorites + * Removes the cache from the favorites. + * + * This must not be called from the UI thread. * * @param cache * the cache to remove @@ -1256,8 +1250,6 @@ public abstract class GCParser { * * @param page * the HTML page to parse, already processed through {@link BaseUtils#replaceWhitespace} - * @param app - * if not null, the application to use to save the trackable * @return the parsed trackable, or null if none could be parsed */ static cgTrackable parseTrackable(final String page, final String possibleTrackingcode) { @@ -1273,7 +1265,7 @@ public abstract class GCParser { final cgTrackable trackable = new cgTrackable(); // trackable geocode - trackable.setGeocode(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, trackable.getGeocode()).toUpperCase()); + trackable.setGeocode(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, trackable.getGeocode())); // trackable id trackable.setGuid(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GUID, true, trackable.getGuid())); @@ -1291,7 +1283,7 @@ public abstract class GCParser { // trackable owner name try { - final Matcher matcherOwner = GCConstants.PATTERN_TRACKABLE_OWNER.matcher(page); + final MatcherWrapper matcherOwner = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_OWNER, page); if (matcherOwner.find() && matcherOwner.groupCount() > 0) { trackable.setOwnerGuid(matcherOwner.group(1)); trackable.setOwner(matcherOwner.group(2).trim()); @@ -1306,14 +1298,14 @@ public abstract class GCParser { // trackable spotted try { - final Matcher matcherSpottedCache = GCConstants.PATTERN_TRACKABLE_SPOTTEDCACHE.matcher(page); + final MatcherWrapper matcherSpottedCache = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_SPOTTEDCACHE, page); if (matcherSpottedCache.find() && matcherSpottedCache.groupCount() > 0) { trackable.setSpottedGuid(matcherSpottedCache.group(1)); trackable.setSpottedName(matcherSpottedCache.group(2).trim()); trackable.setSpottedType(cgTrackable.SPOTTED_CACHE); } - final Matcher matcherSpottedUser = GCConstants.PATTERN_TRACKABLE_SPOTTEDUSER.matcher(page); + final MatcherWrapper matcherSpottedUser = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_SPOTTEDUSER, page); if (matcherSpottedUser.find() && matcherSpottedUser.groupCount() > 0) { trackable.setSpottedGuid(matcherSpottedUser.group(1)); trackable.setSpottedName(matcherSpottedUser.group(2).trim()); @@ -1356,7 +1348,7 @@ public abstract class GCParser { // trackable details & image try { - final Matcher matcherDetailsImage = GCConstants.PATTERN_TRACKABLE_DETAILSIMAGE.matcher(page); + final MatcherWrapper matcherDetailsImage = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_DETAILSIMAGE, page); if (matcherDetailsImage.find() && matcherDetailsImage.groupCount() > 0) { final String image = StringUtils.trim(matcherDetailsImage.group(3)); final String details = StringUtils.trim(matcherDetailsImage.group(4)); @@ -1375,7 +1367,7 @@ public abstract class GCParser { // trackable logs try { - final Matcher matcherLogs = GCConstants.PATTERN_TRACKABLE_LOG.matcher(page); + final MatcherWrapper matcherLogs = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_LOG, page); /* * 1. Type (image) * 2. Date @@ -1404,7 +1396,8 @@ public abstract class GCParser { } // Apply the pattern for images in a trackable log entry against each full log (group(0)) - final Matcher matcherLogImages = GCConstants.PATTERN_TRACKABLE_LOG_IMAGES.matcher(matcherLogs.group(0)); + final String logEntry = matcherLogs.group(0); + final MatcherWrapper matcherLogImages = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_LOG_IMAGES, logEntry); /* * 1. Image URL * 2. Image title @@ -1427,7 +1420,7 @@ public abstract class GCParser { } if (cgeoapplication.getInstance() != null) { - cgeoapplication.getInstance().saveTrackable(trackable); + cgData.saveTrackable(trackable); } return trackable; @@ -1454,7 +1447,7 @@ public abstract class GCParser { String rawResponse; if (!getDataFromPage) { - final Matcher userTokenMatcher = GCConstants.PATTERN_USERTOKEN.matcher(page); + final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page); if (!userTokenMatcher.find()) { Log.e("GCParser.loadLogsFromDetails: unable to extract userToken"); return null; @@ -1553,7 +1546,7 @@ public abstract class GCParser { final List<LogType> types = new ArrayList<LogType>(); - final Matcher typeBoxMatcher = GCConstants.PATTERN_TYPEBOX.matcher(page); + final MatcherWrapper typeBoxMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPEBOX, page); String typesText = null; if (typeBoxMatcher.find()) { if (typeBoxMatcher.groupCount() > 0) { @@ -1563,13 +1556,16 @@ public abstract class GCParser { if (typesText != null) { - final Matcher typeMatcher = GCConstants.PATTERN_TYPE2.matcher(typesText); + final MatcherWrapper typeMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPE2, typesText); while (typeMatcher.find()) { if (typeMatcher.groupCount() > 1) { - final int type = Integer.parseInt(typeMatcher.group(2)); - - if (type > 0) { - types.add(LogType.getById(type)); + try { + int type = Integer.parseInt(typeMatcher.group(2)); + if (type > 0) { + types.add(LogType.getById(type)); + } + } catch (NumberFormatException e) { + Log.e("Error parsing log types", e); } } } @@ -1598,7 +1594,7 @@ public abstract class GCParser { final List<TrackableLog> trackableLogs = new ArrayList<TrackableLog>(); - final Matcher trackableMatcher = GCConstants.PATTERN_TRACKABLE.matcher(page); + final MatcherWrapper trackableMatcher = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE, page); while (trackableMatcher.find()) { if (trackableMatcher.groupCount() > 0) { @@ -1679,4 +1675,48 @@ public abstract class GCParser { } } + public static boolean uploadModifiedCoordinates(cgCache cache, Geopoint wpt) { + return editModifiedCoordinates(cache, wpt); + } + + public static boolean deleteModifiedCoordinates(cgCache cache) { + return editModifiedCoordinates(cache, null); + } + + public static boolean editModifiedCoordinates(cgCache cache, Geopoint wpt) { + final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); + final String userToken = BaseUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + if (StringUtils.isEmpty(userToken)) { + return false; + } + + try { + JSONObject jo; + if (wpt != null) { + jo = new JSONObject().put("dto", (new JSONObject().put("ut", userToken) + .put("data", new JSONObject() + .put("lat", wpt.getLatitudeE6() / 1E6) + .put("lng", wpt.getLongitudeE6() / 1E6)))); + } else { + jo = new JSONObject().put("dto", (new JSONObject().put("ut", userToken))); + } + + 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); + Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); + + if (response != null && response.getStatusLine().getStatusCode() == 200) { + Log.i("GCParser.editModifiedCoordinates - edited on GC.com"); + return true; + } + + } catch (JSONException e) { + Log.e("Unknown exception with json wrap code", e); + } + Log.e("GCParser.deleteModifiedCoordinates - cannot delete modified coords"); + return false; + } + } diff --git a/main/src/cgeo/geocaching/connector/gc/GCSmiliesProvider.java b/main/src/cgeo/geocaching/connector/gc/GCSmiliesProvider.java index 1083a89..eba9301 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCSmiliesProvider.java +++ b/main/src/cgeo/geocaching/connector/gc/GCSmiliesProvider.java @@ -26,7 +26,7 @@ public class GCSmiliesProvider { public final String text; - private Smiley(final String text) { + Smiley(final String text) { this.text = text; } diff --git a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java index 74e78cc..1452157 100644 --- a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java +++ b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java @@ -11,106 +11,191 @@ import android.graphics.Bitmap; */ public abstract class IconDecoder { - public static void parseMapPNG(final cgCache cache, Bitmap bitmap, UTFGridPosition xy, int zoomlevel) { + public static boolean parseMapPNG(final cgCache cache, Bitmap bitmap, UTFGridPosition xy, int zoomlevel) { if (zoomlevel >= 14) { - parseMapPNG14(cache, bitmap, xy); - } else { - parseMapPNG13(cache, bitmap, xy); + return parseMapPNG14(cache, bitmap, xy); } + if (zoomlevel <= 11) { + return parseMapPNG11(cache, bitmap, xy); + } + return parseMapPNG13(cache, bitmap, xy); } - private static final int[] OFFSET_X = new int[] { 0, -1, -1, 0, 1, 1, 1, 0, -1, -2, -2, -2, -2, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2 }; - private static final int[] OFFSET_Y = new int[] { 0, 0, 1, 1, 1, 0, -1, -1, -1, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2, -2, -2, -2, -2 }; + public static int CT_TRADITIONAL = 0; + public static int CT_MULTI = 1; + public static int CT_MYSTERY = 2; + public static int CT_EVENT = 3; + public static int CT_VIRTUAL = 4; + public static int CT_FOUND = 5; + public static int CT_OWN = 6; + public static int CT_MEGAEVENT = 7; + public static int CT_CITO = 8; + public static int CT_WEBCAM = 9; + public static int CT_WHEREIGO = 10; + public static int CT_EARTH = 11; + public static int CT_LETTERBOX = 12; /** - * The icon decoder walks a spiral around the center pixel position of the cache - * and searches for characteristic colors. + * The icon decoder over all 16 pixels of image . It should not be invoked on any part of image that might overlay + * with other caches. + * Is uses decision tree to determine right type. * * @param cache * @param bitmap * @param xy + * @return true if parsing was successful */ - private static void parseMapPNG13(final cgCache cache, Bitmap bitmap, UTFGridPosition xy) { - final int xCenter = xy.getX() * 4 + 2; - final int yCenter = xy.getY() * 4 + 2; + private static boolean parseMapPNG13(final cgCache cache, Bitmap bitmap, UTFGridPosition xy) { + final int topX = xy.getX() * 4; + final int topY = xy.getY() * 4; final int bitmapWidth = bitmap.getWidth(); final int bitmapHeight = bitmap.getHeight(); - int countMulti = 0; - int countFound = 0; + if ((topX < 0) || (topY < 0) || (topX + 4 > bitmapWidth) || (topY + 4 > bitmapHeight)) { + return false; //out of image position + } - for (int i = 0; i < OFFSET_X.length; i++) { + int[] pngType = new int[7]; + for (int x = topX; x < topX + 4; x++) { + for (int y = topY; y < topY + 4; y++) { + int color = bitmap.getPixel(x, y); - // assert that we are still in the tile - final int x = xCenter + OFFSET_X[i]; - if (x < 0 || x >= bitmapWidth) { - continue; - } - - final int y = yCenter + OFFSET_Y[i]; - if (y < 0 || y >= bitmapHeight) { - continue; - } + if ((color & 0xFFFFFF) == 0x5f5f5f) { + continue; //Border in every icon is the same and therefore no use to us + } + if ((color >>> 24) != 255) { + continue; //transparent pixels (or semi_transparent) are only shadows of border + } - int color = bitmap.getPixel(x, y) & 0x00FFFFFF; + int red = (color & 0xFF0000) >> 16; + int green = (color & 0xFF00) >> 8; + int blue = color & 0xFF; - // transparent pixels are not interesting - if (color == 0) { - continue; + int type = getCacheTypeFromPixel13(red, green, blue); + pngType[type]++; } + } - int red = (color & 0xFF0000) >> 16; - int green = (color & 0xFF00) >> 8; - int blue = color & 0xFF; + int type = -1; + int count = 0; - // these are quite sure, so one pixel is enough for matching - if (green > 0x80 && green > red && green > blue) { - cache.setType(CacheType.TRADITIONAL); - return; - } - if (blue > 0x80 && blue > red && blue > green) { - cache.setType(CacheType.MYSTERY); - return; - } - if (red > 0x90 && blue < 0x10 && green < 0x10) { - cache.setType(CacheType.EVENT); - return; + for (int x = 0; x < 7; x++) + { + if (pngType[x] > count) { + count = pngType[x]; + type = x; } + } - // next two are hard to distinguish, therefore we sample all pixels of the spiral - if (red > 0xFA && green > 0xD0) { - countMulti++; + if (count > 1) { // 2 pixels need to detect same type and we say good to go + switch (type) { + case 0: + cache.setType(CacheType.TRADITIONAL); + return true; + case 1: + cache.setType(CacheType.MULTI); + return true; + case 2: + cache.setType(CacheType.MYSTERY); //mystery, whereigo, groundspeak HQ and mystery is most common + return true; + case 3: + cache.setType(CacheType.EVENT); //event, cito, mega-event and event is most common + return true; + case 4: + cache.setType(CacheType.EARTH); //It's an image of ghost (webcam, earth, virtual) and earth in most common + return true; + case 5: + cache.setFound(true); + return true; + case 6: + cache.setOwn(true); + return true; } - if (red < 0xF3 && red > 0xa0 && green > 0x20 && blue < 0x80) { - countFound++; + } + return false; + } + + /** + * The icon decoder over all 16 pixels of image . It should not be invoked on any part of image that might overlay + * with other caches. + * Is uses decision tree to determine right type. + * + * @param cache + * @param bitmap + * @param xy + * @return true if parsing was successful + */ + private static boolean parseMapPNG11(final cgCache cache, Bitmap bitmap, UTFGridPosition xy) { + final int topX = xy.getX() * 4; + final int topY = xy.getY() * 4; + final int bitmapWidth = bitmap.getWidth(); + final int bitmapHeight = bitmap.getHeight(); + + if ((topX < 0) || (topY < 0) || (topX + 4 > bitmapWidth) || (topY + 4 > bitmapHeight)) { + return false; //out of image position + } + + int[] pngType = new int[5]; + for (int x = topX; x < topX + 4; x++) { + for (int y = topY; y < topY + 4; y++) { + int color = bitmap.getPixel(x, y); + + + if ((color >>> 24) != 255) { + continue; //transparent pixels (or semi_transparent) are only shadows of border + } + + int r = (color & 0xFF0000) >> 16; + int g = (color & 0xFF00) >> 8; + int b = color & 0xFF; + + //Duplicate colors does not add any value + if (((r == 52) && (g == 52) && (b == 52)) || + ((r == 69) && (g == 69) && (b == 69)) || + ((r == 90) && (g == 90) && (b == 90)) || + ((r == 233) && (g == 233) && (b == 234)) || + ((r == 255) && (g == 255) && (b == 255))) { + continue; + } + + int type = getCacheTypeFromPixel11(r, g, b); + pngType[type]++; } } - // now check whether we are sure about found/multi - if (countFound > countMulti && countFound >= 2) { - cache.setFound(true); + int type = -1; + int count = 0; + + for (int x = 0; x < 5; x++) + { + if (pngType[x] > count) { + count = pngType[x]; + type = x; + } } - if (countMulti > countFound && countMulti >= 5) { - cache.setType(CacheType.MULTI); + + if (count > 1) { // 2 pixels need to detect same type and we say good to go + switch (type) { + case 0: + cache.setType(CacheType.TRADITIONAL); + return true; + case 1: + cache.setType(CacheType.MULTI); + return true; + case 2: + cache.setType(CacheType.MYSTERY); //mystery, whereigo, groundspeak HQ and mystery is most common + return true; + case 3: + cache.setType(CacheType.EVENT); //event, cito, mega-event and event is most common + return true; + case 4: + cache.setType(CacheType.EARTH); //webcam, earth, virtual and earth in most common + return true; + } } + return false; } - // Pixel colors in tile - private final static int COLOR_BORDER_GRAY = 0x5F5F5F; - private final static int COLOR_TRADITIONAL = 0x316013; - private final static int COLOR_MYSTERY = 0x243C97; - private final static int COLOR_MULTI = 0xFFDE19; - private final static int COLOR_FOUND = 0xFBEA5D; - - // Offset inside cache icon - private final static int POSX_TRADI = 7; - private final static int POSY_TRADI = -12; - private final static int POSX_MULTI = 5; // for orange 8 - private final static int POSY_MULTI = -9; // for orange 10 - private final static int POSX_MYSTERY = 5; - private final static int POSY_MYSTERY = -13; - private final static int POSX_FOUND = 10; - private final static int POSY_FOUND = -8; /** * For level 14 find the borders of the icons and then use a single pixel and color to match. @@ -119,44 +204,376 @@ public abstract class IconDecoder { * @param bitmap * @param xy */ - private static void parseMapPNG14(cgCache cache, Bitmap bitmap, UTFGridPosition xy) { - int x = xy.getX() * 4 + 2; - int y = xy.getY() * 4 + 2; + private static boolean parseMapPNG14(cgCache cache, Bitmap bitmap, UTFGridPosition xy) { + final int topX = xy.getX() * 4; + final int topY = xy.getY() * 4; + final int bitmapWidth = bitmap.getWidth(); + final int bitmapHeight = bitmap.getHeight(); + + if ((topX < 0) || (topY < 0) || (topX + 4 > bitmapWidth) || (topY + 4 > bitmapHeight)) { + return false; //out of image position + } + + int[] pngType = new int[13]; + for (int x = topX; x < topX + 4; x++) { + for (int y = topY; y < topY + 4; y++) { + int color = bitmap.getPixel(x, y); + + if ((color & 0xFFFFFF) == 0x5f5f5f) { + continue; //Border in every icon is the same and therefore no use to us + } + if ((color >>> 24) != 255) { + continue; //transparent pixels (or semi_transparent) are only shadows of border + } + + int r = (color & 0xFF0000) >> 16; + int g = (color & 0xFF00) >> 8; + int b = color & 0xFF; + + //Duplicate colors does not add any value + if (((r == 216) && (g == 216) && (b == 216)) || + ((r == 23) && (g == 23) && (b == 23)) || + ((r == 240) && (g == 240) && (b == 240)) || + ((r == 44) && (g == 44) && (b == 44)) || + ((r == 228) && (g == 228) && (b == 228)) || + ((r == 225) && (g == 225) && (b == 225)) || + ((r == 199) && (g == 199) && (b == 199)) || + ((r == 161) && (g == 161) && (b == 161)) || + ((r == 8) && (g == 8) && (b == 8)) || + ((r == 200) && (g == 200) && (b == 200)) || + ((r == 255) && (g == 255) && (b == 255)) || + ((r == 250) && (g == 250) && (b == 250)) || + ((r == 95) && (g == 95) && (b == 95)) || + ((r == 236) && (g == 236) && (b == 236)) || + ((r == 215) && (g == 215) && (b == 215)) || + ((r == 232) && (g == 232) && (b == 232)) || + ((r == 217) && (g == 217) && (b == 217)) || + ((r == 0) && (g == 0) && (b == 0)) || + ((r == 167) && (g == 167) && (b == 167)) || + ((r == 247) && (g == 247) && (b == 247)) || + ((r == 144) && (g == 144) && (b == 144)) || + ((r == 231) && (g == 231) && (b == 231)) || + ((r == 248) && (g == 248) && (b == 248))) { + continue; + } - // search for left border - int countX = 0; - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != COLOR_BORDER_GRAY) { - if (--x < 0 || ++countX > 20) { - return; + int type = getCacheTypeFromPixel14(r, g, b); + pngType[type]++; } } - // search for bottom border - int countY = 0; - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != 0x000000) { - if (++y >= Tile.TILE_SIZE || ++countY > 20) { - return; + + int type = -1; + int count = 0; + + for (int x = 0; x < 7; x++) + { + if (pngType[x] > count) { + count = pngType[x]; + type = x; + } + } + /* + * public static int CT_MEGAEVENT = 7; + * public static int CT_CITO = 8; + * public static int CT_WEBCAM = 9; + * public static int CT_WHEREIGO = 10; + * public static int CT_EARTH = 11; + * public static int CT_LETTERBOX = 12; + */ + if (count > 1) { // 2 pixels need to detect same type and we say good to go + switch (type) { + case 0: + cache.setType(CacheType.TRADITIONAL); + return true; + case 1: + cache.setType(CacheType.MULTI); + return true; + case 2: + cache.setType(CacheType.MYSTERY); + return true; + case 3: + cache.setType(CacheType.EVENT); + return true; + case 4: + cache.setType(CacheType.VIRTUAL); + return true; + case 5: + cache.setFound(true); + return true; + case 6: + cache.setOwn(true); + return true; + case 7: + cache.setType(CacheType.MEGA_EVENT); + return true; + case 8: + cache.setType(CacheType.CITO); + return true; + case 9: + cache.setType(CacheType.WEBCAM); + return true; + case 10: + cache.setType(CacheType.WHERIGO); + return true; + case 11: + cache.setType(CacheType.EARTH); + return true; + case 12: + cache.setType(CacheType.LETTERBOX); + return true; + } + } + return false; + + } + + /** + * This method returns detected type from specific pixel from geocaching.com live map. + * It was constructed based on classification tree made by Orange (http://orange.biolab.si/) + * Input file was made from every non-transparent pixel of every possible "middle" cache icon from GC map + * + * @param r + * Red component of pixel (from 0 - 255) + * @param g + * Green component of pixel (from 0 - 255) + * @param b + * Blue component of pixel (from 0 - 255) + * @return Value from 0 to 6 representing detected type or state of the cache. + */ + private static int getCacheTypeFromPixel13(int r, int g, int b) { + if (g < 110) { + if (r > 87) { + return ((g > 73) && (b < 63)) ? CT_FOUND : CT_EVENT; + } + return CT_MYSTERY; + } + if (b > 137) { + if ((r < 184) && (g > 190)) { + return CT_TRADITIONAL; + } + if ((r < 184) && (g < 191) && (r < 136)) { + return CT_MYSTERY; } + return CT_VIRTUAL; } + if (r < 158) { + return ((r > 129) && (r < 153)) ? CT_FOUND : CT_TRADITIONAL; + } + if (b > 33) { + if (b > 57) { + if (b > 100) { + return (r > 229) ? CT_MULTI : CT_EVENT; + } + return ((r > 254) && (g < 236)) ? CT_MULTI : CT_FOUND; + } + if ((g > 173) && ((g < 224))) { + return ((r < 243) && (r > 223)) ? CT_FOUND : CT_OWN; + } + return CT_FOUND; + } + return CT_MULTI; + } - try { - if ((bitmap.getPixel(x + POSX_TRADI, y + POSY_TRADI) & 0x00FFFFFF) == COLOR_TRADITIONAL) { - cache.setType(CacheType.TRADITIONAL); - return; + /** + * This method returns detected type from specific pixel from geocaching.com live map level 14 or higher. + * It was constructed based on classification tree made by Orange (http://orange.biolab.si/) + * Input file was made from every non-transparent pixel of every possible "full" cache icon from GC map + * + * @param r + * Red component of pixel (from 0 - 255) + * @param g + * Green component of pixel (from 0 - 255) + * @param b + * Blue component of pixel (from 0 - 255) + * @return Value from 0 to 6 representing detected type or state of the cache. + */ + private static int getCacheTypeFromPixel14(int r, int g, int b) { + if (b < 140) { + if (r > 155) { + if (g < 159) { + if (r < 173) { + return (r > 161) ? CT_MEGAEVENT : CT_OWN; + } + if (r < 206) { + if (b > 49) { + return (b > 83) ? CT_EVENT : CT_FOUND; + } + return (b < 31) ? CT_EARTH : CT_FOUND; + } + return (r < 221) ? CT_FOUND : CT_MULTI; + } + if (r > 210) { + if (g < 188) { + return CT_FOUND; + } + if (r < 246) { + return CT_OWN; + } + if (r < 254) { + return CT_FOUND; + } + if (r < 255) { + return CT_EVENT; + } + if (g < 208) { + return CT_EARTH; + } + if (g > 225) { + return CT_EARTH; + } + return (b < 36) ? CT_MULTI : CT_OWN; + } + return (b < 66) ? CT_OWN : CT_EARTH; + } + if (r < 63) { + if (b > 26) { + if (b < 29) { + return CT_WEBCAM; + } + if (g > 102) { + return CT_CITO; + } + return (r < 26) ? CT_CITO : CT_WEBCAM; + } + if (g < 38) { + return CT_WEBCAM; + } + return (r < 41) ? CT_EARTH : CT_TRADITIONAL; + } + if (b < 119) { + if (g < 81) { + return CT_WEBCAM; + } + if (b < 90) { + return CT_OWN; + } + return (r < 104) ? CT_WEBCAM : CT_OWN; + } + if (r < 132) { + return (b < 124) ? CT_MULTI : CT_WHEREIGO; + } + if (g > 164) { + return CT_TRADITIONAL; + } + if (b < 134) { + return CT_OWN; + } + return (b > 137) ? CT_OWN : CT_WHEREIGO; + } + if (b < 245) { + if (r < 180) { + if (b < 218) { + if (g < 71) { + return CT_MYSTERY; + } + if (r < 96) { + return CT_WHEREIGO; + } + if (b > 165) { + return CT_WHEREIGO; + } + if (r < 153) { + return CT_WHEREIGO; + } + if (r < 160) { + return CT_WEBCAM; + } + return (r < 162) ? CT_WHEREIGO : CT_WEBCAM; + } + return (r < 158) ? CT_MEGAEVENT : CT_EARTH; } - if ((bitmap.getPixel(x + POSX_MYSTERY, y + POSY_MYSTERY) & 0x00FFFFFF) == COLOR_MYSTERY) { - cache.setType(CacheType.MYSTERY); - return; + if (g > 232) { + if (g > 247) { + return CT_CITO; + } + if (r < 237) { + return CT_OWN; + } + if (g < 238) { + return CT_OWN; + } + if (r > 243) { + return CT_WEBCAM; + } + return (g > 238) ? CT_OWN : CT_WEBCAM; } - if ((bitmap.getPixel(x + POSX_MULTI, y + POSY_MULTI) & 0x00FFFFFF) == COLOR_MULTI) { - cache.setType(CacheType.MULTI); - return; + if (r < 228) { + if (b > 238) { + return CT_MYSTERY; + } + if (r < 193) { + if (r < 184) { + return CT_OWN; + } + if (g < 186) { + return CT_WHEREIGO; + } + return (r > 189) ? CT_WHEREIGO : CT_OWN; + } + if (g < 223) { + if (r > 216) { + return CT_OWN; + } + if (g > 217) { + return CT_WHEREIGO; + } + if (b > 211) { + return CT_WEBCAM; + } + if (b < 196) { + return CT_WEBCAM; + } + if (r > 210) { + return CT_OWN; + } + return (g > 206) ? CT_WHEREIGO : CT_OWN; + } + if (g < 224) { + return CT_OWN; + } + return (r < 226) ? CT_WHEREIGO : CT_OWN; } - if ((bitmap.getPixel(x + POSX_FOUND, y + POSY_FOUND) & 0x00FFFFFF) == COLOR_FOUND) { - cache.setFound(true); + return (b < 216) ? CT_FOUND : CT_OWN; + } + if (r < 238) { + if (r > 141) { + return (r > 185) ? CT_LETTERBOX : CT_CITO; } - } catch (IllegalArgumentException e) { - // intentionally left blank + return (r < 41) ? CT_EARTH : CT_LETTERBOX; } + return (r < 243) ? CT_WHEREIGO : CT_OWN; + } + /** + * This method returns detected type from specific pixel from geocaching.com live map level 11 or lower. + * It was constructed based on classification tree made by Orange (http://orange.biolab.si/) + * Input file was made from every non-transparent pixel of every possible "full" cache icon from GC map + * + * @param r + * Red component of pixel (from 0 - 255) + * @param g + * Green component of pixel (from 0 - 255) + * @param b + * Blue component of pixel (from 0 - 255) + * @return Value from 0 to 4 representing detected type or state of the cache. + */ + private static int getCacheTypeFromPixel11(int r, int g, int b) { + if (b < 139) { + if (g > 104) { + if (r < 173) { + return CT_TRADITIONAL; + } + return (r > 225) ? CT_MULTI : CT_EVENT; + } + if (b < 25) { + return CT_EVENT; + } + return (r < 87) ? CT_MYSTERY : CT_EVENT; + } + if (r > 140) { + return (r < 197) ? CT_TRADITIONAL : CT_VIRTUAL; + } + return CT_MYSTERY; } + } diff --git a/main/src/cgeo/geocaching/connector/gc/Login.java b/main/src/cgeo/geocaching/connector/gc/Login.java index c3f29cc..9a60f65 100644 --- a/main/src/cgeo/geocaching/connector/gc/Login.java +++ b/main/src/cgeo/geocaching/connector/gc/Login.java @@ -10,6 +10,7 @@ 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 ch.boye.httpclientandroidlib.HttpResponse; @@ -26,7 +27,6 @@ import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.regex.Matcher; public abstract class Login { @@ -349,7 +349,7 @@ public abstract class Login { } int count = 1; - final Matcher matcherViewstateCount = GCConstants.PATTERN_VIEWSTATEFIELDCOUNT.matcher(page); + final MatcherWrapper matcherViewstateCount = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATEFIELDCOUNT, page); if (matcherViewstateCount.find()) { try { count = Integer.parseInt(matcherViewstateCount.group(1)); @@ -361,14 +361,13 @@ public abstract class Login { String[] viewstates = new String[count]; // Get the viewstates - int no; - final Matcher matcherViewstates = GCConstants.PATTERN_VIEWSTATES.matcher(page); + final MatcherWrapper matcherViewstates = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATES, page); while (matcherViewstates.find()) { String sno = matcherViewstates.group(1); // number of viewstate - if (sno.length() == 0) { + int no; + if (StringUtils.isEmpty(sno)) { no = 0; - } - else { + } else { try { no = Integer.parseInt(sno); } catch (NumberFormatException e) { diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java index 5404446..73ded4d 100644 --- a/main/src/cgeo/geocaching/connector/gc/Tile.java +++ b/main/src/cgeo/geocaching/connector/gc/Tile.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.Locale; import java.util.Set; /** @@ -99,7 +100,7 @@ public class Tile { /** * Calculate latitude/longitude for a given x/y position in this tile. - * + * * @see <a * href="http://developers.cloudmade.com/projects/tiles/examples/convert-coordinates-to-tile-numbers">Cloudmade</a> */ @@ -115,7 +116,7 @@ public class Tile { @Override public String toString() { - return String.format("(%d/%d), zoom=%d", tileX, tileY, zoomlevel); + return String.format(Locale.US, "(%d/%d), zoom=%d", tileX, tileY, zoomlevel); } /** diff --git a/main/src/cgeo/geocaching/connector/gc/UTFGrid.java b/main/src/cgeo/geocaching/connector/gc/UTFGrid.java index a4eeff5..6d20eb6 100644 --- a/main/src/cgeo/geocaching/connector/gc/UTFGrid.java +++ b/main/src/cgeo/geocaching/connector/gc/UTFGrid.java @@ -29,7 +29,7 @@ public final class UTFGrid { } /** Calculate from a list of positions (x/y) the coords */ - protected static UTFGridPosition getPositionInGrid(List<UTFGridPosition> positions) { + public static UTFGridPosition getPositionInGrid(List<UTFGridPosition> positions) { int minX = GRID_MAXX; int maxX = 0; int minY = GRID_MAXY; diff --git a/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java b/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java index e34d277..5965fff 100644 --- a/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java +++ b/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java @@ -1,6 +1,7 @@ package cgeo.geocaching.connector.gc; -import java.util.regex.Matcher; +import cgeo.geocaching.utils.MatcherWrapper; + import java.util.regex.Pattern; @@ -35,7 +36,7 @@ public final class UTFGridPosition { * @return */ static UTFGridPosition fromString(String key) { - final Matcher matcher = UTFGridPosition.PATTERN_JSON_KEY.matcher(key); + final MatcherWrapper matcher = new MatcherWrapper(UTFGridPosition.PATTERN_JSON_KEY, key); try { if (matcher.matches()) { final int x = Integer.parseInt(matcher.group(1)); diff --git a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java new file mode 100644 index 0000000..d0c0e16 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java @@ -0,0 +1,514 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.LogEntry; +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgCache; +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.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.Pattern; + +public class OC11XMLParser { + + private static Pattern STRIP_DATE = Pattern.compile("\\+0([0-9]){1}\\:00"); + + private static class CacheHolder { + public cgCache cache; + public String latitude; + public String longitude; + } + + private static class CacheLog { + public String cacheId; + public LogEntry logEntry; + } + + private static class CacheDescription { + public String cacheId; + public String shortDesc; + public String desc; + public String hint; + } + + 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) { + 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; + } + return CacheSize.NOT_CHOSEN; + } + + private static CacheType getCacheType(final String typeId) { + 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.UNKNOWN; + case 10: // Driv./Drive-In + return CacheType.TRADITIONAL; + } + 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; + } + return LogType.UNKNOWN; + } + + private static void setCacheStatus(final int statusId, final cgCache 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 cgCache(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; + } + + public static Collection<cgCache> parseCaches(final InputStream stream) throws IOException { + + final int CACHE_PARSE_LIMIT = 250; + + final Map<String, cgCache> caches = new HashMap<String, cgCache>(); + + 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"); + + // cache + cacheNode.setStartElementListener(new StartElementListener() { + + @Override + public void start(Attributes attributes) { + resetCache(cacheHolder); + } + + }); + + cacheNode.setEndElementListener(new EndElementListener() { + + @Override + public void end() { + cgCache cache = cacheHolder.cache; + Geopoint coords = new Geopoint(cacheHolder.latitude, cacheHolder.longitude); + if (StringUtils.isNotBlank(cache.getGeocode()) + && !coords.equals(Geopoint.ZERO) + && !cache.isArchived() + && caches.size() < CACHE_PARSE_LIMIT) { + cache.setCoords(coords); + caches.put(cache.getCacheId(), cache); + } + } + }); + + // 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(String.format("Listed on geocaching com: <a href=\"http://coord.info/%s\">%s</a><br /><br />", gccode, gccode)); + } + } + } + }); + + // 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(); + cacheHolder.cache.setDifficulty(Float.valueOf(content)); + } + }); + + // cache.terrain + cacheNode.getChild("terrain").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + final String content = body.trim(); + cacheHolder.cache.setTerrain(Float.valueOf(content)); + } + }); + + // cache.terrain + cacheNode.getChild("datehidden").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + final String content = body.trim(); + cacheHolder.cache.setHidden(parseFullDate(content)); + } + }); + + // cache.attributes.attribute + cacheNode.getChild("attributes").getChild("attribute").setEndTextElementListener(new EndTextElementListener() { + @Override + public void end(String body) { + 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 cgCache cache = caches.get(descHolder.cacheId); + if (cache != null) { + cache.setShortdesc(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 = content; + } + }); + + // cachedesc.desc + cacheDesc.getChild("desc").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + final String content = body.trim(); + descHolder.desc = content; + } + }); + + // cachedesc.hint + cacheDesc.getChild("hint").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + final String content = body.trim(); + descHolder.hint = content; + } + }); + + // 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 cgCache cache = caches.get(logHolder.cacheId); + if (cache != null && logHolder.logEntry.type != LogType.UNKNOWN) { + cache.getLogs().prepend(logHolder.logEntry); + if (logHolder.logEntry.type == LogType.FOUND_IT + && StringUtils.equals(logHolder.logEntry.author, Settings.getOCConnectorUserName())) { + cache.setFound(true); + cache.setVisitedDate(logHolder.logEntry.date); + } + } + } + }); + + // 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 (Exception e) { + Log.w("Failed to parse log date: " + e.toString()); + } + } + }); + + // cachelog.logtype + cacheLog.getChild("logtype").setStartElementListener(new StartElementListener() { + + @Override + public void start(Attributes attrs) { + if (attrs.getIndex("id") > -1) { + final int typeId = Integer.parseInt(attrs.getValue("id")); + logHolder.logEntry.type = getLogType(typeId); + } + } + }); + + // 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 = logText; + } + }); + + 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.toString()); + return null; + } + } +} diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java index 17c961a..74968e7 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java @@ -34,4 +34,10 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { } return new SearchResult(cache); } + + @Override + public boolean isActivated() { + // currently always active, but only for details download + return true; + } } diff --git a/main/src/cgeo/geocaching/connector/oc/OCConnector.java b/main/src/cgeo/geocaching/connector/oc/OCConnector.java index c098d12..24fd7d6 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCConnector.java @@ -2,6 +2,7 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.cgCache; import cgeo.geocaching.connector.AbstractConnector; +import cgeo.geocaching.enumerations.CacheRealm; import java.util.regex.Pattern; @@ -51,4 +52,9 @@ public class OCConnector extends AbstractConnector { return "http://" + host + "/viewcache.php?wp="; } + @Override + public CacheRealm getCacheRealm() { + return CacheRealm.OC; + } + } diff --git a/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java new file mode 100644 index 0000000..3a2f42e --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java @@ -0,0 +1,52 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.SearchResult; +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgCache; +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.utils.CancellableHandler; + +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 cgCache 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(); + } + +} diff --git a/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java b/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java new file mode 100644 index 0000000..26b42e3 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java @@ -0,0 +1,110 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.cgCache; +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.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +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 cgCache getCache(final String geoCode) { + try { + final Parameters params = getOCXmlQueryParameters(true, true); + params.put("wp", geoCode); + final InputStream data = request(ConnectorFactory.getConnector(geoCode), SERVICE_CACHE, params); + + if (data == null) { + return null; + } + + Collection<cgCache> caches = OC11XMLParser.parseCaches(new GZIPInputStream(data)); + if (caches.iterator().hasNext()) { + cgCache cache = caches.iterator().next(); + cache.setDetailed(true); + cgData.saveCache(cache, LoadFlags.SAVE_ALL); + return cache; + } + return null; + } catch (IOException e) { + Log.e("Error parsing cache '" + geoCode + "': " + e.toString()); + return null; + } + } + + @SuppressWarnings("unchecked") + public static Collection<cgCache> getCachesAround(final Geopoint center, final double distance) { + try { + final Parameters params = getOCXmlQueryParameters(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 CollectionUtils.EMPTY_COLLECTION; + } + + return OC11XMLParser.parseCaches(new GZIPInputStream(data)); + } catch (IOException e) { + Log.e("Error parsing nearby search result: " + e.toString()); + return CollectionUtils.EMPTY_COLLECTION; + } + } + + 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 Parameters params = new Parameters("modifiedsince", "20060320000000", + "user", "0", "cache", "1", "cachedesc", withDescription ? "1" : "0", + "cachelog", withLogs ? "1" : "0", "picture", "0", "removedobject", "0", + "session", "0", "doctype", "0", "charset", "utf-8", "zip", "gzip"); + return params; + } +} diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 87cc3a1..1121cc5 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -2,8 +2,8 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.LogEntry; import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgData; import cgeo.geocaching.cgImage; -import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.enumerations.CacheSize; @@ -153,9 +153,8 @@ final public class OkapiClient { final JSONArray images = response.getJSONArray(CACHE_IMAGES); if (images != null) { - JSONObject imageResponse; for (int i = 0; i < images.length(); i++) { - imageResponse = images.getJSONObject(i); + 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()); @@ -171,9 +170,8 @@ final public class OkapiClient { cache.setUpdated(System.currentTimeMillis()); cache.setDetailedUpdate(cache.getUpdated()); cache.setDetailed(true); - // save full detailed caches - cgeoapplication.getInstance().saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); } catch (JSONException e) { Log.e("OkapiClient.parseCache", e); } |
