PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java

http://github.com/cgeo/c-geo-opensource
Java | 453 lines | 363 code | 46 blank | 44 comment | 50 complexity | ed89310a72d43b745b285612dff12366 MD5 | raw file
Possible License(s): Apache-2.0
  1. package cgeo.geocaching.connector.trackable;
  2. import cgeo.geocaching.CgeoApplication;
  3. import cgeo.geocaching.R;
  4. import cgeo.geocaching.enumerations.StatusCode;
  5. import cgeo.geocaching.location.Geopoint;
  6. import cgeo.geocaching.log.AbstractLoggingActivity;
  7. import cgeo.geocaching.log.LogTypeTrackable;
  8. import cgeo.geocaching.log.TrackableLog;
  9. import cgeo.geocaching.models.Geocache;
  10. import cgeo.geocaching.models.Trackable;
  11. import cgeo.geocaching.network.Network;
  12. import cgeo.geocaching.network.Parameters;
  13. import cgeo.geocaching.settings.Settings;
  14. import cgeo.geocaching.storage.DataStore;
  15. import cgeo.geocaching.utils.Log;
  16. import cgeo.geocaching.utils.Version;
  17. import android.content.Context;
  18. import androidx.annotation.NonNull;
  19. import androidx.annotation.Nullable;
  20. import java.io.InputStream;
  21. import java.net.URLEncoder;
  22. import java.util.Calendar;
  23. import java.util.Collections;
  24. import java.util.List;
  25. import java.util.Locale;
  26. import java.util.TimeZone;
  27. import java.util.regex.Pattern;
  28. import io.reactivex.rxjava3.core.Observable;
  29. import io.reactivex.rxjava3.functions.Function;
  30. import org.apache.commons.collections4.CollectionUtils;
  31. import org.apache.commons.compress.utils.IOUtils;
  32. import org.apache.commons.lang3.StringUtils;
  33. import org.apache.commons.lang3.tuple.ImmutablePair;
  34. import org.xml.sax.InputSource;
  35. public class GeokretyConnector extends AbstractTrackableConnector {
  36. /*
  37. 1) tracking code:
  38. is generated from the alphabet:
  39. "a b c d e f g h i j k l m n p q r s t u v w x y z 1 2 3 4 5 6 7 8 9"
  40. (no O and 0)
  41. sanity-check for tracking code: if generated code look like reference
  42. number (ie GKxxxx):
  43. preg_match("#^gk[0-9a-f]{4}$#i", $tc)
  44. 2) reference number (GKxxxx):
  45. it is just a subsequent number in the database ($id) converted to hex:
  46. $gk=sprintf("GK%04X",$id);
  47. $id=hexdec(substr($gk, 2, 4));
  48. */
  49. private static final Pattern PATTERN_GK_CODE = Pattern.compile("GK[0-9A-F]{4,}");
  50. private static final Pattern PATTERN_GK_CODE_EXTENDED = Pattern.compile("(GK[0-9A-F]{4,})|([1-9A-NP-Z]{6})");
  51. private static final String HOST = "geokrety.org";
  52. public static final String URL = "https://" + HOST;
  53. private static final String URLPROXY = "https://api." + HOST;
  54. @Override
  55. @NonNull
  56. public String getHost() {
  57. return HOST;
  58. }
  59. @Override
  60. @NonNull
  61. public String getHostUrl() {
  62. return URL;
  63. }
  64. @Override
  65. @Nullable
  66. public String getProxyUrl() {
  67. return URLPROXY;
  68. }
  69. @Override
  70. public int getPreferenceActivity() {
  71. return R.string.preference_screen_geokrety;
  72. }
  73. @Override
  74. public boolean canHandleTrackable(@Nullable final String geocode) {
  75. return geocode != null && PATTERN_GK_CODE.matcher(geocode).matches();
  76. }
  77. @Override
  78. public boolean canHandleTrackable(@Nullable final String geocode, @Nullable final TrackableBrand brand) {
  79. if (brand != TrackableBrand.GEOKRETY) {
  80. return canHandleTrackable(geocode);
  81. }
  82. return geocode != null && PATTERN_GK_CODE_EXTENDED.matcher(geocode).matches();
  83. }
  84. @Override
  85. @NonNull
  86. public String getServiceTitle() {
  87. return CgeoApplication.getInstance().getString(R.string.init_geokrety);
  88. }
  89. @Override
  90. @NonNull
  91. public String getUrl(@NonNull final Trackable trackable) {
  92. //https://geokrety.org/konkret.php?id=46464
  93. return URL + "/konkret.php?id=" + getId(trackable.getGeocode());
  94. }
  95. @Override
  96. @Nullable
  97. public Trackable searchTrackable(final String geocode, final String guid, final String id) {
  98. return searchTrackable(geocode);
  99. }
  100. @Nullable
  101. public static Trackable searchTrackable(final String geocode) {
  102. final int gkid;
  103. if (StringUtils.startsWithIgnoreCase(geocode, "GK")) {
  104. gkid = getId(geocode);
  105. if (gkid < 0) {
  106. Log.d("GeokretyConnector.searchTrackable: Unable to retrieve GK numeric ID by ReferenceNumber");
  107. return null;
  108. }
  109. } else {
  110. // This is probably a Tracking Code
  111. Log.d("GeokretyConnector.searchTrackable: geocode=" + geocode);
  112. final String geocodeFound = getGeocodeFromTrackingCode(geocode);
  113. if (geocodeFound == null) {
  114. Log.d("GeokretyConnector.searchTrackable: Unable to retrieve trackable by TrackingCode");
  115. return null;
  116. }
  117. gkid = getId(geocodeFound);
  118. }
  119. Log.d("GeokretyConnector.searchTrackable: gkid=" + gkid);
  120. try {
  121. final String[] gkUlrs = {
  122. URLPROXY + "/gk/" + gkid + "/details",
  123. URL + "/export2.php" + "?gkid=" + gkid,
  124. };
  125. InputStream response = null;
  126. for (final String urlDetails : gkUlrs) {
  127. response = Network.getResponseStream(Network.getRequest(urlDetails));
  128. if (response != null) {
  129. break;
  130. }
  131. Log.d("GeokretyConnector.searchTrackable: No data from address " + urlDetails);
  132. }
  133. if (response == null) {
  134. Log.d("GeokretyConnector.searchTrackable: No data for gkid " + gkid);
  135. return null;
  136. }
  137. try {
  138. final InputSource is = new InputSource(response);
  139. final List<Trackable> trackables = GeokretyParser.parse(is);
  140. if (CollectionUtils.isNotEmpty(trackables)) {
  141. final Trackable trackable = trackables.get(0);
  142. DataStore.saveTrackable(trackable);
  143. return trackable;
  144. }
  145. } finally {
  146. IOUtils.closeQuietly(response);
  147. }
  148. } catch (final Exception e) {
  149. Log.w("GeokretyConnector.searchTrackable", e);
  150. }
  151. return null;
  152. }
  153. @Override
  154. @NonNull
  155. public List<Trackable> searchTrackables(final String geocode) {
  156. Log.d("GeokretyConnector.searchTrackables: wpt=" + geocode);
  157. try {
  158. final String geocodeEncoded = URLEncoder.encode(geocode, "utf-8");
  159. final String[] gkUlrs = {
  160. URLPROXY + "/wpt/" + geocodeEncoded,
  161. URL + "/export2.php?wpt=" + geocodeEncoded,
  162. };
  163. InputStream response = null;
  164. for (final String urlDetails : gkUlrs) {
  165. response = Network.getResponseStream(Network.getRequest(urlDetails));
  166. if (response != null) {
  167. break;
  168. }
  169. Log.d("GeokretyConnector.searchTrackables: No data from from address " + urlDetails);
  170. }
  171. if (response == null) {
  172. Log.d("GeokretyConnector.searchTrackables: No data for geocode " + geocode);
  173. return Collections.emptyList();
  174. }
  175. try {
  176. final InputSource is = new InputSource(response);
  177. return GeokretyParser.parse(is);
  178. } finally {
  179. IOUtils.closeQuietly(response);
  180. }
  181. } catch (final Exception e) {
  182. Log.w("GeokretyConnector.searchTrackables", e);
  183. return Collections.emptyList();
  184. }
  185. }
  186. @Override
  187. @NonNull
  188. public List<Trackable> loadInventory() {
  189. return loadInventory(0);
  190. }
  191. @NonNull
  192. private static List<Trackable> loadInventory(final int userid) {
  193. Log.d("GeokretyConnector.loadInventory: userid=" + userid);
  194. try {
  195. final Parameters params = new Parameters("inventory", "1");
  196. if (userid > 0) {
  197. // retrieve someone inventory
  198. params.put("userid", String.valueOf(userid));
  199. } else {
  200. if (StringUtils.isBlank(Settings.getGeokretySecId())) {
  201. return Collections.emptyList();
  202. }
  203. // Retrieve inventory, with tracking codes
  204. params.put("secid", Settings.getGeokretySecId());
  205. }
  206. final InputStream response = Network.getResponseStream(Network.getRequest(URL + "/export2.php", params));
  207. if (response == null) {
  208. Log.d("GeokretyConnector.loadInventory: No data from server");
  209. return Collections.emptyList();
  210. }
  211. try {
  212. final InputSource is = new InputSource(response);
  213. return GeokretyParser.parse(is);
  214. } finally {
  215. IOUtils.closeQuietly(response);
  216. }
  217. } catch (final Exception e) {
  218. Log.w("GeokretyConnector.loadInventory", e);
  219. return Collections.emptyList();
  220. }
  221. }
  222. @Override
  223. @NonNull
  224. public Observable<TrackableLog> trackableLogInventory() {
  225. return Observable.fromIterable(loadInventory()).map(new TrackableLogFunction());
  226. }
  227. private static class TrackableLogFunction implements Function<Trackable, TrackableLog> {
  228. @Override
  229. public TrackableLog apply(final Trackable trackable) {
  230. return new TrackableLog(
  231. trackable.getGeocode(),
  232. trackable.getTrackingcode(),
  233. trackable.getName(),
  234. getId(trackable.getGeocode()),
  235. 0,
  236. trackable.getBrand()
  237. );
  238. }
  239. }
  240. public static int getId(final String geocode) {
  241. try {
  242. final String hex = geocode.substring(2);
  243. return Integer.parseInt(hex, 16);
  244. } catch (final NumberFormatException e) {
  245. Log.e("GeokretyConnector.getId", e);
  246. }
  247. return -1;
  248. }
  249. @Override
  250. @Nullable
  251. public String getTrackableCodeFromUrl(@NonNull final String url) {
  252. // https://geokrety.org/konkret.php?id=38545
  253. final String gkId = StringUtils.substringAfterLast(url, "konkret.php?id=");
  254. if (StringUtils.isNumeric(gkId)) {
  255. return geocode(Integer.parseInt(gkId));
  256. }
  257. // https://api.geokrety.org/gk/38545
  258. // https://api.geokrety.org/gk/38545/details
  259. String gkapiId = StringUtils.substringAfterLast(url, "api.geokrety.org/gk/");
  260. gkapiId = StringUtils.substringBeforeLast(gkapiId, "/");
  261. if (StringUtils.isNumeric(gkapiId)) {
  262. return geocode(Integer.parseInt(gkapiId));
  263. }
  264. return null;
  265. }
  266. @Override
  267. @Nullable
  268. public String getTrackableTrackingCodeFromUrl(@NonNull final String url) {
  269. // http://geokrety.org/m/qr.php?nr=<TRACKING_CODE>
  270. final String gkTrackingCode = StringUtils.substringAfterLast(url, "qr.php?nr=");
  271. if (StringUtils.isAlphanumeric(gkTrackingCode)) {
  272. return gkTrackingCode;
  273. }
  274. return null;
  275. }
  276. /**
  277. * Lookup Trackable Geocode from Tracking Code.
  278. *
  279. * @param trackingCode
  280. * the Trackable Tracking Code to lookup
  281. * @return
  282. * the Trackable Geocode
  283. */
  284. @Nullable
  285. private static String getGeocodeFromTrackingCode(final String trackingCode) {
  286. final String response = Network.getResponseData(Network.getRequest(URLPROXY + "/nr2id/" + trackingCode));
  287. // An empty response means "not found"
  288. if (response == null || StringUtils.equals(response, "0")) {
  289. return null;
  290. }
  291. return geocode(Integer.parseInt(response));
  292. }
  293. @Override
  294. @NonNull
  295. public TrackableBrand getBrand() {
  296. return TrackableBrand.GEOKRETY;
  297. }
  298. @Override
  299. public boolean isGenericLoggable() {
  300. return true;
  301. }
  302. @Override
  303. public boolean isActive() {
  304. return Settings.isGeokretyConnectorActive();
  305. }
  306. @Override
  307. public boolean isRegistered() {
  308. return Settings.isRegisteredForGeokretyLogging() && isActive();
  309. }
  310. @Override
  311. public boolean recommendLogWithGeocode() {
  312. return true;
  313. }
  314. @Override
  315. public AbstractTrackableLoggingManager getTrackableLoggingManager(final AbstractLoggingActivity activity) {
  316. return new GeokretyLoggingManager(activity);
  317. }
  318. /**
  319. * Get geocode from GeoKrety id
  320. *
  321. */
  322. public static String geocode(final int id) {
  323. return String.format("GK%04X", id);
  324. }
  325. @Override
  326. public boolean isLoggable() {
  327. return true;
  328. }
  329. public static ImmutablePair<StatusCode, List<String>> postLogTrackable(final Context context, final Geocache cache, final TrackableLog trackableLog, final Calendar date, final String log) {
  330. // See doc: http://geokrety.org/api.php
  331. Log.d("GeokretyConnector.postLogTrackable: nr=" + trackableLog.trackCode);
  332. if (trackableLog.brand != TrackableBrand.GEOKRETY) {
  333. Log.d("GeokretyConnector.postLogTrackable: received invalid brand");
  334. return new ImmutablePair<>(StatusCode.LOG_POST_ERROR_GK, Collections.emptyList());
  335. }
  336. if (trackableLog.action == LogTypeTrackable.DO_NOTHING) {
  337. Log.d("GeokretyConnector.postLogTrackable: received invalid logtype");
  338. return new ImmutablePair<>(StatusCode.LOG_POST_ERROR_GK, Collections.emptyList());
  339. }
  340. try {
  341. // SecId is mandatory when using API, anonymous log are only possible via website
  342. final String secId = Settings.getGeokretySecId();
  343. if (StringUtils.isEmpty(secId)) {
  344. Log.d("GeokretyConnector.postLogTrackable: not authenticated");
  345. return new ImmutablePair<>(StatusCode.NO_LOGIN_INFO_STORED, Collections.emptyList());
  346. }
  347. // XXX: Use always CET timezone for Geokrety logging
  348. // See https://github.com/cgeo/cgeo/issues/9496
  349. date.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
  350. // Construct Post Parameters
  351. final Parameters params = new Parameters(
  352. "secid", secId,
  353. "gzip", "0",
  354. "nr", trackableLog.trackCode,
  355. "formname", "ruchy",
  356. "logtype", String.valueOf(trackableLog.action.gkid),
  357. "data", String.format(Locale.ENGLISH, "%tY-%tm-%td", date, date, date), // YYYY-MM-DD
  358. "godzina", String.format("%tH", date), // HH
  359. "minuta", String.format("%tM", date), // MM
  360. "comment", log,
  361. "app", context.getString(R.string.app_name),
  362. "app_ver", Version.getVersionName(context),
  363. "mobile_lang", Settings.getApplicationLocale().toString() + ".UTF-8"
  364. );
  365. // See doc: http://geokrety.org/help.php#acceptableformats
  366. if (cache != null) {
  367. final Geopoint coords = cache.getCoords();
  368. if (coords != null) {
  369. params.add("latlon", coords.toString());
  370. }
  371. final String geocode = cache.getGeocode();
  372. if (StringUtils.isNotEmpty(geocode)) {
  373. params.add("wpt", geocode);
  374. }
  375. }
  376. final String page = Network.getResponseData(Network.postRequest(URL + "/ruchy.php", params));
  377. if (page == null) {
  378. Log.d("GeokretyConnector.postLogTrackable: No data from server");
  379. return new ImmutablePair<>(StatusCode.CONNECTION_FAILED_GK, Collections.emptyList());
  380. }
  381. final ImmutablePair<Integer, List<String>> response = GeokretyParser.parseResponse(page);
  382. if (response == null) {
  383. Log.w("GeokretyConnector.postLogTrackable: Cannot parseResponse GeoKrety");
  384. return new ImmutablePair<>(StatusCode.LOG_POST_ERROR_GK, Collections.emptyList());
  385. }
  386. final List<String> errors = response.getRight();
  387. if (CollectionUtils.isNotEmpty(errors)) {
  388. for (final String error: errors) {
  389. Log.w("GeokretyConnector.postLogTrackable: " + error);
  390. }
  391. return new ImmutablePair<>(StatusCode.LOG_POST_ERROR_GK, errors);
  392. }
  393. Log.i("Geokrety Log successfully posted to trackable #" + trackableLog.trackCode);
  394. return new ImmutablePair<>(StatusCode.NO_ERROR, Collections.emptyList());
  395. } catch (final RuntimeException e) {
  396. Log.w("GeokretyConnector.searchTrackable", e);
  397. return new ImmutablePair<>(StatusCode.LOG_POST_ERROR_GK, Collections.emptyList());
  398. }
  399. }
  400. public static String getCreateAccountUrl() {
  401. return URL + "/adduser.php";
  402. }
  403. }