<commit_before>package cgeo.geocaching;

import cgeo.geocaching.compatibility.Compatibility;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.location.GeopointFormatter.Format;
import cgeo.geocaching.models.Geocache;
import cgeo.geocaching.models.Waypoint;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.storage.LocalStorage;
import cgeo.geocaching.utils.AndroidRxUtils;
import cgeo.geocaching.utils.FileUtils;
import cgeo.geocaching.utils.Log;

import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
import rx.Completable;
import rx.functions.Func0;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

public final class StaticMapsProvider {
    static final int MAPS_LEVEL_MAX = 5;
    private static final String PREFIX_PREVIEW = "preview";
    private static final String GOOGLE_STATICMAP_URL = "https://maps.google.com/maps/api/staticmap";
    private static final int GOOGLE_MAX_ZOOM = 20;
    private static final String SATELLITE = "satellite";
    private static final String ROADMAP = "roadmap";
    private static final String WAYPOINT_PREFIX = "wp";
    private static final String MAP_FILENAME_PREFIX = "map_";
    private static final String MARKERS_URL = "https://cgeo-status.herokuapp.com/assets/markers/";

    private static volatile long last403 = 0;

    /** We assume there is no real usable image with less than 1k. */
    private static final int MIN_MAP_IMAGE_BYTES = 1000;

    /**
     * max size in free API version: https://developers.google.com/maps/documentation/staticmaps/#Imagesizes
     */
    private static final int GOOGLE_MAPS_MAX_SIZE = 640;

    private StaticMapsProvider() {
        // utility class
    }

    private static File getMapFile(final String geocode, final String prefix, final boolean createDirs) {
        return LocalStorage.getStorageFile(geocode, MAP_FILENAME_PREFIX + prefix, false, createDirs);
    }

    private static Completable checkDownloadPermission(final Completable ifPermitted) {
        return Completable.defer(new Func0<Completable>() {
            @Override
            public Completable call() {
                if (System.currentTimeMillis() - last403 >= 30000) {
                    return ifPermitted;
                }
                Log.d("StaticMaps.downloadMap: request ignored because of recent \"permission denied\" answer");
                return Completable.complete();
            }
        });
    }

    private static Completable downloadDifferentZooms(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int width, final int height, final Parameters waypoints) {
        return checkDownloadPermission(Completable.merge(downloadMap(geocode, 20, SATELLITE, markerUrl, prefix + '1', "", latlonMap, width, height, waypoints),
                downloadMap(geocode, 18, SATELLITE, markerUrl, prefix + '2', "", latlonMap, width, height, waypoints),
                downloadMap(geocode, 16, ROADMAP, markerUrl, prefix + '3', "", latlonMap, width, height, waypoints),
                downloadMap(geocode, 14, ROADMAP, markerUrl, prefix + '4', "", latlonMap, width, height, waypoints),
                downloadMap(geocode, 11, ROADMAP, markerUrl, prefix + '5', "", latlonMap, width, height, waypoints)));
    }

    private static Completable downloadMap(final String geocode, final int zoom, final String mapType, final String markerUrl, final String prefix, final String shadow, final String latlonMap, final int width, final int height, final Parameters waypoints) {
        // If it has been less than 30 seconds since we got a 403 (permission denied) from Google servers,
        // do not try again.
        final int scale = width <= GOOGLE_MAPS_MAX_SIZE ? 1 : 2;
        final float aspectRatio = width / (float) height;
        final int requestWidth = Math.min(width / scale, GOOGLE_MAPS_MAX_SIZE);
        final int requestHeight = (aspectRatio > 1) ? Math.round(requestWidth / aspectRatio) : requestWidth;
        final int requestZoom = Math.min((scale == 2) ? zoom + 1 : zoom, GOOGLE_MAX_ZOOM);
        return checkDownloadPermission(Completable.defer(new Func0<Completable>() {
            @Override
            public Completable call() {
                final Parameters params = new Parameters(
                        "center", latlonMap,
                        "zoom", String.valueOf(requestZoom),
                        "size", String.valueOf(requestWidth) + 'x' + String.valueOf(requestHeight),
                        "scale", String.valueOf(scale),
                        "maptype", mapType,
                        "markers", "icon:" + markerUrl + '|' + shadow + latlonMap,
                        "sensor", "false");
                if (waypoints != null) {
                    params.addAll(waypoints);
                }
                try {
                    final Response httpResponse = Network.getRequest(GOOGLE_STATICMAP_URL, params).toBlocking().value();

                    final int statusCode = httpResponse.code();
                    if (statusCode != 200) {
                        Log.d("StaticMapsProvider.downloadMap: httpResponseCode = " + statusCode);
                        if (statusCode == 403) {
                            last403 = System.currentTimeMillis();
                        }
                        return Completable.complete();
                    }
                    final File file = getMapFile(geocode, prefix, true);
                    if (LocalStorage.saveEntityToFile(httpResponse, file)) {
                        // Delete image if it has no contents
                        final long fileSize = file.length();
                        if (fileSize < MIN_MAP_IMAGE_BYTES) {
                            FileUtils.deleteIgnoringFailure(file);
                        }
                    }
                } catch (final Exception ignored) {
                    Log.e("StaticMapsProvider.downloadMap: error");
                }
                return Completable.complete();
            }
        }).subscribeOn(AndroidRxUtils.networkScheduler));
    }

    public static Completable downloadMaps(final Geocache cache) {
        if ((!Settings.isStoreOfflineMaps() && !Settings.isStoreOfflineWpMaps()) || StringUtils.isBlank(cache.getGeocode())) {
            return Completable.complete();
        }
        // TODO Check if this is also OK, was width -25
        final Point displaySize = Compatibility.getDisplaySize();

        final List<Completable> downloaders = new LinkedList<>();

        if (Settings.isStoreOfflineMaps() && cache.getCoords() != null) {
            downloaders.add(storeCachePreviewMap(cache));
            downloaders.add(storeCacheStaticMap(cache, displaySize.x, displaySize.y));
        }
<commit_msg>Fix #5705, Freeze in cache details without network<commit_after>package cgeo.geocaching;

import cgeo.geocaching.compatibility.Compatibility;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.location.GeopointFormatter.Format;
import cgeo.geocaching.models.Geocache;
import cgeo.geocaching.models.Waypoint;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.storage.LocalStorage;
import cgeo.geocaching.utils.AndroidRxUtils;
import cgeo.geocaching.utils.FileUtils;
import cgeo.geocaching.utils.Log;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

import okhttp3.Response;
import rx.Completable;
import rx.functions.Func0;

public final class StaticMapsProvider {
    static final int MAPS_LEVEL_MAX = 5;
    private static final String PREFIX_PREVIEW = "preview";
    private static final String GOOGLE_STATICMAP_URL = "https://maps.google.com/maps/api/staticmap";
    private static final int GOOGLE_MAX_ZOOM = 20;
    private static final String SATELLITE = "satellite";
    private static final String ROADMAP = "roadmap";
    private static final String WAYPOINT_PREFIX = "wp";
    private static final String MAP_FILENAME_PREFIX = "map_";
    private static final String MARKERS_URL = "https://cgeo-status.herokuapp.com/assets/markers/";

    private static volatile long last403 = 0;

    /** We assume there is no real usable image with less than 1k. */
    private static final int MIN_MAP_IMAGE_BYTES = 1000;

    /**
     * max size in free API version: https://developers.google.com/maps/documentation/staticmaps/#Imagesizes
     */
    private static final int GOOGLE_MAPS_MAX_SIZE = 640;

    private StaticMapsProvider() {
        // utility class
    }

    private static File getMapFile(final String geocode, final String prefix, final boolean createDirs) {
        return LocalStorage.getStorageFile(geocode, MAP_FILENAME_PREFIX + prefix, false, createDirs);
    }

    private static Completable checkDownloadPermission(final Completable ifPermitted) {
        return Completable.defer(new Func0<Completable>() {
            @Override
            public Completable call() {
                if (System.currentTimeMillis() - last403 >= 30000) {
                    return ifPermitted;
                }
                Log.d("StaticMaps.downloadMap: request ignored because of recent \"permission denied\" answer");
                return Completable.complete();
            }
        });
    }

    private static Completable downloadDifferentZooms(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int width, final int height, final Parameters waypoints) {
        return checkDownloadPermission(Completable.merge(downloadMap(geocode, 20, SATELLITE, markerUrl, prefix + '1', "", latlonMap, width, height, waypoints),
                downloadMap(geocode, 18, SATELLITE, markerUrl, prefix + '2', "", latlonMap, width, height, waypoints),
                downloadMap(geocode, 16, ROADMAP, markerUrl, prefix + '3', "", latlonMap, width, height, waypoints),
                downloadMap(geocode, 14, ROADMAP, markerUrl, prefix + '4', "", latlonMap, width, height, waypoints),
                downloadMap(geocode, 11, ROADMAP, markerUrl, prefix + '5', "", latlonMap, width, height, waypoints)));
    }

    private static Completable downloadMap(final String geocode, final int zoom, final String mapType, final String markerUrl, final String prefix, final String shadow, final String latlonMap, final int width, final int height, final Parameters waypoints) {
        // If it has been less than 30 seconds since we got a 403 (permission denied) from Google servers,
        // do not try again.
        final int scale = width <= GOOGLE_MAPS_MAX_SIZE ? 1 : 2;
        final float aspectRatio = width / (float) height;
        final int requestWidth = Math.min(width / scale, GOOGLE_MAPS_MAX_SIZE);
        final int requestHeight = (aspectRatio > 1) ? Math.round(requestWidth / aspectRatio) : requestWidth;
        final int requestZoom = Math.min((scale == 2) ? zoom + 1 : zoom, GOOGLE_MAX_ZOOM);
        return checkDownloadPermission(Completable.defer(new Func0<Completable>() {
            @Override
            public Completable call() {
                final Parameters params = new Parameters(
                        "center", latlonMap,
                        "zoom", String.valueOf(requestZoom),
                        "size", String.valueOf(requestWidth) + 'x' + String.valueOf(requestHeight),
                        "scale", String.valueOf(scale),
                        "maptype", mapType,
                        "markers", "icon:" + markerUrl + '|' + shadow + latlonMap,
                        "sensor", "false");
                if (waypoints != null) {
                    params.addAll(waypoints);
                }
                try {
                    final Response httpResponse = Network.getRequest(GOOGLE_STATICMAP_URL, params).toBlocking().value();

                    final int statusCode = httpResponse.code();
                    if (statusCode != 200) {
                        Log.d("StaticMapsProvider.downloadMap: httpResponseCode = " + statusCode);
                        if (statusCode == 403) {
                            last403 = System.currentTimeMillis();
                        }
                        return Completable.complete();
                    }
                    final File file = getMapFile(geocode, prefix, true);
                    if (LocalStorage.saveEntityToFile(httpResponse, file)) {
                        // Delete image if it has no contents
                        final long fileSize = file.length();
                        if (fileSize < MIN_MAP_IMAGE_BYTES) {
                            FileUtils.deleteIgnoringFailure(file);
                        }
                    }
                } catch (final Exception ex) {
                    Log.w("StaticMapsProvider.downloadMap: error", ex);
                }
                return Completable.complete();
            }
        }).subscribeOn(AndroidRxUtils.networkScheduler));
    }

    public static Completable downloadMaps(final Geocache cache) {
        if ((!Settings.isStoreOfflineMaps() && !Settings.isStoreOfflineWpMaps()) || StringUtils.isBlank(cache.getGeocode())) {
            return Completable.complete();
        }
        // TODO Check if this is also OK, was width -25
        final Point displaySize = Compatibility.getDisplaySize();

        final List<Completable> downloaders = new LinkedList<>();

        if (Settings.isStoreOfflineMaps() && cache.getCoords() != null) {
            downloaders.add(storeCachePreviewMap(cache));
            downloaders.add(storeCacheStaticMap(cache, displaySize.x, displaySize.y));
        }

{'fraction_non_alphanumeric': 0.09379202944715792, 'fraction_numerical': 0.009343809726056487, 'mean_word_length': 2.4901185770750986, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 34, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3131150', 'n_tokens_mistral': 3927, 'n_tokens_neox': 3664, 'n_words': 988}