Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Feature: Change path of the resources cache db (CP #13707) #13947

Merged
merged 3 commits into from Mar 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/mbgl/storage/default_file_source.hpp
Expand Up @@ -47,6 +47,8 @@ class DefaultFileSource : public FileSource {

void setResourceTransform(optional<ActorRef<ResourceTransform>>&&);

void setResourceCachePath(const std::string&);

std::unique_ptr<AsyncRequest> request(const Resource&, Callback) override;

/*
Expand Down
Expand Up @@ -9,11 +9,9 @@
import android.support.annotation.NonNull;

import com.mapbox.mapboxsdk.LibraryLoader;
import com.mapbox.mapboxsdk.MapStrictMode;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.net.ConnectivityReceiver;
import com.mapbox.mapboxsdk.storage.FileSource;
Expand Down Expand Up @@ -136,23 +134,8 @@ private OfflineManager(Context context) {
}

private void deleteAmbientDatabase(final Context context) {
// Delete the file in a separate thread to avoid affecting the UI
new Thread(new Runnable() {
@Override
public void run() {
try {
String path = FileSource.getInternalCachePath(context) + File.separator + "mbgl-cache.db";
File file = new File(path);
if (file.exists()) {
file.delete();
Logger.d(TAG, String.format("Old ambient cache database deleted to save space: %s", path));
}
} catch (Exception exception) {
Logger.e(TAG, "Failed to delete old ambient cache database: ", exception);
MapStrictMode.strictModeViolation(exception);
}
}
}).start();
final String path = FileSource.getInternalCachePath(context) + File.separator + "mbgl-cache.db";
FileUtils.deleteFile(path);
}

/**
Expand Down Expand Up @@ -469,17 +452,17 @@ private native void createOfflineRegion(FileSource fileSource, OfflineRegionDefi
* resource were requested in the process of map rendering.
* Use this method to pre-warm the cache with resources you know
* will be requested.
*
* <p>
* This call is asynchronous: the data may not be immediately available
* for in-progress requests, although subsequent requests should have
* access to the cached data.
*
* @param url The URL of the resource to insert
* @param data Response data to store for this resource. Data is expected to be uncompressed;
* internally, the cache will compress data as necessary.
* @param modified Optional "modified" response header, in seconds since 1970, or 0 if not set
* @param expires Optional "expires" response header, in seconds since 1970, or 0 if not set
* @param etag Optional "entity tag" response header
* @param url The URL of the resource to insert
* @param data Response data to store for this resource. Data is expected to be uncompressed;
* internally, the cache will compress data as necessary.
* @param modified Optional "modified" response header, in seconds since 1970, or 0 if not set
* @param expires Optional "expires" response header, in seconds since 1970, or 0 if not set
* @param etag Optional "entity tag" response header
* @param mustRevalidate Indicates whether response can be used after it's stale
*/
@Keep
Expand Down
@@ -1,6 +1,7 @@
package com.mapbox.mapboxsdk.storage;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
Expand All @@ -14,20 +15,24 @@
import com.mapbox.mapboxsdk.MapStrictMode;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.utils.FileUtils;
import com.mapbox.mapboxsdk.utils.ThreadUtils;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.mapbox.mapboxsdk.log.Logger;

/**
* Holds a central reference to the core's DefaultFileSource for as long as
* there are active mapviews / offline managers
*/
public class FileSource {

private static final String TAG = "Mbgl-FileSource";
private static final String MAPBOX_SHARED_PREFERENCES = "MapboxSharedPreferences";
private static final String MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH = "fileSourceResourcesCachePath";
private static final Lock resourcesCachePathLoaderLock = new ReentrantLock();
private static final Lock internalCachePathLoaderLock = new ReentrantLock();
@Nullable
Expand All @@ -53,6 +58,29 @@ public interface ResourceTransformCallback {

}

/**
* This callback receives an asynchronous response containing the new path of the
* resources cache database.
*/
@Keep
public interface ResourcesCachePathChangeCallback {

/**
* Receives the new database path
*
* @param path the path of the current resources cache database
*/
void onSuccess(String path);

/**
* Receives an error message if setting the path was not successful
*
* @param message the error message
*/
void onError(String message);

}

// File source instance is kept alive after initialization
private static FileSource INSTANCE;

Expand All @@ -79,6 +107,40 @@ public static synchronized FileSource getInstance(@NonNull Context context) {
*/
@NonNull
private static String getCachePath(@NonNull Context context) {
SharedPreferences preferences = context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE);
String cachePath = preferences.getString(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH, null);

if (!isPathWritable(cachePath)) {
// Use default path
cachePath = getDefaultCachePath(context);

// Reset stored cache path
SharedPreferences.Editor editor =
context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit();
editor.remove(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH).apply();
}

return cachePath;
}

/**
* Get the default resources cache path depending on the external storage configuration
*
* @param context the context to derive the files directory path from
* @return the default directory path
*/
@NonNull
private static String getDefaultCachePath(@NonNull Context context) {
if (isExternalStorageConfiguration(context) && isExternalStorageReadable()) {
File externalFilesDir = context.getExternalFilesDir(null);
if (externalFilesDir != null) {
return externalFilesDir.getAbsolutePath();
}
}
return context.getFilesDir().getAbsolutePath();
}

private static boolean isExternalStorageConfiguration(@NonNull Context context) {
// Default value
boolean isExternalStorageConfiguration = MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL;

Expand All @@ -99,24 +161,7 @@ private static String getCachePath(@NonNull Context context) {
Logger.e(TAG, "Failed to read the storage key: ", exception);
MapStrictMode.strictModeViolation(exception);
}

String cachePath = null;
if (isExternalStorageConfiguration && isExternalStorageReadable()) {
try {
// Try getting the external storage path
cachePath = context.getExternalFilesDir(null).getAbsolutePath();
} catch (NullPointerException exception) {
Logger.e(TAG, "Failed to obtain the external storage path: ", exception);
MapStrictMode.strictModeViolation(exception);
}
}

if (cachePath == null) {
// Default to internal storage
cachePath = context.getFilesDir().getAbsolutePath();
}

return cachePath;
return isExternalStorageConfiguration;
}

/**
Expand Down Expand Up @@ -217,6 +262,85 @@ public static String getInternalCachePath(@NonNull Context context) {
}
}

/**
* Changes the path of the resources cache database.
* Note that the external storage setting needs to be activated in the manifest.
*
* @param context the context of the path
* @param path the new database path
* @param callback the callback to obtain the result
*/
public static void setResourcesCachePath(@NonNull Context context,
@NonNull final String path,
@NonNull ResourcesCachePathChangeCallback callback) {
final String fileSourceActivatedMessage = "Cannot set path, file source is activated."
+ " Make sure that the map or a resources download is not running.";
if (getInstance(context).isActivated()) {
Logger.w(TAG, fileSourceActivatedMessage);
callback.onError(fileSourceActivatedMessage);
} else if (path.equals(resourcesCachePath)) {
// no need to change the path
callback.onSuccess(path);
} else {
final WeakReference<Context> contextWeakReference = new WeakReference<>(context);
final WeakReference<ResourcesCachePathChangeCallback> callbackWeakReference = new WeakReference<>(callback);
new FileUtils.CheckFileWritePermissionTask(new FileUtils.OnCheckFileWritePermissionListener() {
@Override
public void onWritePermissionGranted() {
final Context context = contextWeakReference.get();
final ResourcesCachePathChangeCallback callback = callbackWeakReference.get();

if (callback == null) {
Logger.w(TAG, "Lost callback reference.");
return;
} else if (context == null) {
String lostContextMessage = "Lost context reference.";
Logger.w(TAG, lostContextMessage);
callback.onError(lostContextMessage);
return;
}

// verify fileSource's activation again after the async task returns
if (getInstance(context).isActivated()) {
Logger.w(TAG, fileSourceActivatedMessage);
callback.onError(fileSourceActivatedMessage);
} else {
final SharedPreferences.Editor editor =
context.getSharedPreferences(MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE).edit();
editor.putString(MAPBOX_SHARED_PREFERENCE_RESOURCES_CACHE_PATH, path);
editor.apply();
setResourcesCachePath(context, path);
callback.onSuccess(path);
}
}

@Override
public void onError() {
final ResourcesCachePathChangeCallback callback = callbackWeakReference.get();
if (callback != null) {
String message = "Path is not writable: " + path;
Logger.e(TAG, message);
callback.onError(message);
}
}
}).execute(new File(path));
}
}
LukasPaczos marked this conversation as resolved.
Show resolved Hide resolved

private static void setResourcesCachePath(@NonNull Context context, @NonNull String path) {
resourcesCachePathLoaderLock.lock();
resourcesCachePath = path;
resourcesCachePathLoaderLock.unlock();
getInstance(context).setResourceCachePath(path);
}

private static boolean isPathWritable(String path) {
if (path == null || path.isEmpty()) {
return false;
}
return new File(path).canWrite();
}

private static void lockPathLoaders() {
internalCachePathLoaderLock.lock();
resourcesCachePathLoaderLock.lock();
Expand Down Expand Up @@ -264,6 +388,9 @@ private FileSource(String cachePath, AssetManager assetManager) {
@Keep
public native void setResourceTransform(final ResourceTransformCallback callback);

@Keep
private native void setResourceCachePath(String path);

@Keep
private native void initialize(String accessToken, String cachePath, AssetManager assetManager);

Expand Down
Expand Up @@ -3,11 +3,15 @@
import android.os.AsyncTask;
import android.support.annotation.NonNull;

import com.mapbox.mapboxsdk.log.Logger;

import java.io.File;
import java.lang.ref.WeakReference;

public class FileUtils {

private static final String TAG = "Mbgl-FileUtils";

/**
* Task checking whether app's process can read a file.
*/
Expand Down Expand Up @@ -121,4 +125,30 @@ public interface OnCheckFileWritePermissionListener {
*/
void onError();
}

/**
* Deletes a file asynchronously in a separate thread.
*
* @param path the path of the file that should be deleted
*/
public static void deleteFile(@NonNull final String path) {
// Delete the file in a separate thread to avoid affecting the UI
new Thread(new Runnable() {
@Override
public void run() {
try {
File file = new File(path);
if (file.exists()) {
if (file.delete()) {
Logger.d(TAG, "File deleted to save space: " + path);
} else {
Logger.e(TAG, "Failed to delete file: " + path);
}
}
} catch (Exception exception) {
Logger.e(TAG, "Failed to delete file: ", exception);
}
}
}).start();
}
}
Expand Up @@ -316,6 +316,17 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.offline.ChangeResourcesCachePathActivity"
android:description="@string/description_change_resources_cache_path"
android:label="@string/activity_change_resources_cache_path">
<meta-data
android:name="@string/category"
android:value="@string/category_offline" />
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.imagegenerator.SnapshotActivity"
android:description="@string/description_snapshot"
Expand Down