Skip to content
127 changes: 74 additions & 53 deletions parse/src/main/java/com/parse/Parse.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.parse.boltsinternal.Continuation;
import com.parse.boltsinternal.Task;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
Expand All @@ -26,6 +29,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;

import okhttp3.OkHttpClient;

/**
Expand Down Expand Up @@ -75,7 +79,7 @@ private Parse() {
* }
* }
* </pre>
*
* <p>
* See <a
* href="https://github.com/parse-community/Parse-SDK-Android/issues/279">https://github.com/parse-community/Parse-SDK-Android/issues/279</a>
* for a discussion on performance of local datastore, and if it is right for your project.
Expand All @@ -85,8 +89,8 @@ private Parse() {
public static void enableLocalDatastore(Context context) {
if (isInitialized()) {
throw new IllegalStateException(
"`Parse#enableLocalDatastore(Context)` must be invoked "
+ "before `Parse#initialize(Context)`");
"`Parse#enableLocalDatastore(Context)` must be invoked "
+ "before `Parse#initialize(Context)`");
}
isLocalDatastoreEnabled = true;
}
Expand All @@ -113,7 +117,7 @@ public static boolean isLocalDatastoreEnabled() {

/**
* @return {@code True} if {@link Configuration.Builder#allowCustomObjectId()} has been called,
* otherwise {@code false}.
* otherwise {@code false}.
*/
public static boolean isAllowCustomObjectId() {
return allowCustomObjectId;
Expand Down Expand Up @@ -145,6 +149,9 @@ static void initialize(Configuration configuration, ParsePlugins parsePlugins) {
PLog.w(TAG, "Parse is already initialized");
return;
}
// Perform old dir migration on initialize.
new ParseCacheDirMigrationUtils(configuration.context).runMigrations();

// NOTE (richardross): We will need this here, as ParsePlugins uses the return value of
// isLocalDataStoreEnabled() to perform additional behavior.
isLocalDatastoreEnabled = configuration.localDataStoreEnabled;
Expand Down Expand Up @@ -176,32 +183,32 @@ static void initialize(Configuration configuration, ParsePlugins parsePlugins) {
checkCacheApplicationId();
final Context context = configuration.context;
Task.callInBackground(
(Callable<Void>)
() -> {
getEventuallyQueue(context);
return null;
});
(Callable<Void>)
() -> {
getEventuallyQueue(context);
return null;
});

ParseFieldOperations.registerDefaultDecoders();

if (!allParsePushIntentReceiversInternal()) {
throw new SecurityException(
"To prevent external tampering to your app's notifications, "
+ "all receivers registered to handle the following actions must have "
+ "their exported attributes set to false: com.parse.push.intent.RECEIVE, "
+ "com.parse.push.intent.OPEN, com.parse.push.intent.DELETE");
"To prevent external tampering to your app's notifications, "
+ "all receivers registered to handle the following actions must have "
+ "their exported attributes set to false: com.parse.push.intent.RECEIVE, "
+ "com.parse.push.intent.OPEN, com.parse.push.intent.DELETE");
}

ParseUser.getCurrentUserAsync()
.makeVoid()
.continueWith(
(Continuation<Void, Void>)
task -> {
// Prime config in the background
ParseConfig.getCurrentConfig();
return null;
},
Task.BACKGROUND_EXECUTOR);
.makeVoid()
.continueWith(
(Continuation<Void, Void>)
task -> {
// Prime config in the background
ParseConfig.getCurrentConfig();
return null;
},
Task.BACKGROUND_EXECUTOR);

dispatchOnParseInitialized();

Expand All @@ -213,8 +220,11 @@ static void initialize(Configuration configuration, ParsePlugins parsePlugins) {

// region Server URL

/** Returns the current server URL. */
public static @Nullable String getServer() {
/**
* Returns the current server URL.
*/
public static @Nullable
String getServer() {
URL server = ParseRESTCommand.server;
return server == null ? null : server.toString();
}
Expand Down Expand Up @@ -248,7 +258,8 @@ public static void setServer(@NonNull String server) {
* @param server The server URL to validate.
* @return The validated server URL.
*/
private static @Nullable String validateServerUrl(@Nullable String server) {
private static @Nullable
String validateServerUrl(@Nullable String server) {

// Add an extra trailing slash so that Parse REST commands include
// the path as part of the server URL (i.e. http://api.myhost.com/parse)
Expand Down Expand Up @@ -285,7 +296,9 @@ public static void destroy() {
allowCustomObjectId = false;
}

/** @return {@code True} if {@link #initialize} has been called, otherwise {@code false}. */
/**
* @return {@code True} if {@link #initialize} has been called, otherwise {@code false}.
*/
static boolean isInitialized() {
return ParsePlugins.get() != null;
}
Expand All @@ -308,10 +321,10 @@ public static Context getApplicationContext() {
*/
private static boolean allParsePushIntentReceiversInternal() {
List<ResolveInfo> intentReceivers =
ManifestInfo.getIntentReceivers(
ParsePushBroadcastReceiver.ACTION_PUSH_RECEIVE,
ParsePushBroadcastReceiver.ACTION_PUSH_DELETE,
ParsePushBroadcastReceiver.ACTION_PUSH_OPEN);
ManifestInfo.getIntentReceivers(
ParsePushBroadcastReceiver.ACTION_PUSH_RECEIVE,
ParsePushBroadcastReceiver.ACTION_PUSH_DELETE,
ParsePushBroadcastReceiver.ACTION_PUSH_OPEN);

for (ResolveInfo resolveInfo : intentReceivers) {
if (resolveInfo.activityInfo.exported) {
Expand Down Expand Up @@ -414,15 +427,15 @@ private static ParseEventuallyQueue getEventuallyQueue(Context context) {
synchronized (MUTEX) {
boolean isLocalDatastoreEnabled = Parse.isLocalDatastoreEnabled();
if (eventuallyQueue == null
|| (isLocalDatastoreEnabled && eventuallyQueue instanceof ParseCommandCache)
|| (!isLocalDatastoreEnabled
&& eventuallyQueue instanceof ParsePinningEventuallyQueue)) {
|| (isLocalDatastoreEnabled && eventuallyQueue instanceof ParseCommandCache)
|| (!isLocalDatastoreEnabled
&& eventuallyQueue instanceof ParsePinningEventuallyQueue)) {
checkContext();
ParseHttpClient httpClient = ParsePlugins.get().restClient();
eventuallyQueue =
isLocalDatastoreEnabled
? new ParsePinningEventuallyQueue(context, httpClient)
: new ParseCommandCache(context, httpClient);
isLocalDatastoreEnabled
? new ParsePinningEventuallyQueue(context, httpClient)
: new ParseCommandCache(context, httpClient);

// We still need to clear out the old command cache even if we're using Pinning in
// case
Expand All @@ -436,33 +449,35 @@ private static ParseEventuallyQueue getEventuallyQueue(Context context) {
}
}

/** Used by Parse LiveQuery */
/**
* Used by Parse LiveQuery
*/
public static void checkInit() {
if (ParsePlugins.get() == null) {
throw new RuntimeException(
"You must call Parse.initialize(Context)" + " before using the Parse library.");
"You must call Parse.initialize(Context)" + " before using the Parse library.");
}

if (ParsePlugins.get().applicationId() == null) {
throw new RuntimeException(
"applicationId is null. "
+ "You must call Parse.initialize(Context)"
+ " before using the Parse library.");
"applicationId is null. "
+ "You must call Parse.initialize(Context)"
+ " before using the Parse library.");
}
}

static void checkContext() {
if (ParsePlugins.get().applicationContext() == null) {
throw new RuntimeException(
"applicationContext is null. "
+ "You must call Parse.initialize(Context)"
+ " before using the Parse library.");
"applicationContext is null. "
+ "You must call Parse.initialize(Context)"
+ " before using the Parse library.");
}
}

static boolean hasPermission(String permission) {
return (getApplicationContext().checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED);
== PackageManager.PERMISSION_GRANTED);
}

// endregion
Expand All @@ -472,10 +487,10 @@ static boolean hasPermission(String permission) {
static void requirePermission(String permission) {
if (!hasPermission(permission)) {
throw new IllegalStateException(
"To use this functionality, add this to your AndroidManifest.xml:\n"
+ "<uses-permission android:name=\""
+ permission
+ "\" />");
"To use this functionality, add this to your AndroidManifest.xml:\n"
+ "<uses-permission android:name=\""
+ permission
+ "\" />");
}
}

Expand All @@ -489,7 +504,7 @@ static void requirePermission(String permission) {
static void registerParseCallbacks(ParseCallbacks listener) {
if (isInitialized()) {
throw new IllegalStateException(
"You must register callbacks before Parse.initialize(Context)");
"You must register callbacks before Parse.initialize(Context)");
}

synchronized (MUTEX_CALLBACKS) {
Expand Down Expand Up @@ -537,7 +552,9 @@ private static ParseCallbacks[] collectParseCallbacks() {
return callbacks;
}

/** Returns the level of logging that will be displayed. */
/**
* Returns the level of logging that will be displayed.
*/
public static int getLogLevel() {
return PLog.getLogLevel();
}
Expand Down Expand Up @@ -569,7 +586,9 @@ interface ParseCallbacks {
void onParseInitialized();
}

/** Represents an opaque configuration for the {@code Parse} SDK configuration. */
/**
* Represents an opaque configuration for the {@code Parse} SDK configuration.
*/
public static final class Configuration {
final Context context;
final String applicationId;
Expand All @@ -591,7 +610,9 @@ private Configuration(Builder builder) {
this.maxRetries = builder.maxRetries;
}

/** Allows for simple constructing of a {@code Configuration} object. */
/**
* Allows for simple constructing of a {@code Configuration} object.
*/
public static final class Builder {
private final Context context;
private String applicationId;
Expand Down
106 changes: 106 additions & 0 deletions parse/src/main/java/com/parse/ParseCacheDirMigrationUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.parse;

import android.content.Context;

import java.io.File;
import java.util.ArrayList;

/**
* The {@code ParseMigrationUtils} class perform caching dir migration operation for {@code Parse} SDK.
*/
public class ParseCacheDirMigrationUtils {
private final String TAG = this.getClass().getName();
private final Object lock = new Object();
private final Context context;

protected ParseCacheDirMigrationUtils(Context context) {
this.context = context;
}

/*Start old data migrations to new respective locations ("/files/com.parse/", "/cache/com.parse/")*/
protected void runMigrations() {
synchronized (lock) {
runSilentMigration(context);
}
}

private void runSilentMigration(Context context) {
ArrayList<File> filesToBeMigrated = new ArrayList<>();
ParseFileUtils.getAllNestedFiles(
getOldParseDir(context).getAbsolutePath(),
filesToBeMigrated
);
if (filesToBeMigrated.isEmpty()) {
return;
}
boolean useFilesDir = false;
//Hard coded config file names list.
String[] configNamesList = {"installationId", "currentUser", "currentConfig", "currentInstallation", "LocalId", "pushState"};
//Start migration for each files in `allFiles`.
for (File itemToMove : filesToBeMigrated) {
try {
for (String configName : configNamesList) {
if (itemToMove.getAbsolutePath().contains(configName)) {
useFilesDir = true;
break;
} else {
useFilesDir = false;
}
}
File fileToSave = new File(
(useFilesDir ? context.getFilesDir() : context.getCacheDir())
+ "/com.parse/" +
getFileOldDir(context, itemToMove),
itemToMove.getName());
//Perform copy operation if file doesn't exist in the new directory.
if (!fileToSave.exists()) {
ParseFileUtils.copyFile(itemToMove, fileToSave);
logMigrationStatus(itemToMove.getName(), itemToMove.getPath(), fileToSave.getAbsolutePath(), "Successful.");
} else {
logMigrationStatus(itemToMove.getName(), itemToMove.getPath(), fileToSave.getAbsolutePath(), "Already exist in new location.");
}
ParseFileUtils.deleteQuietly(itemToMove);
PLog.v(TAG, "File deleted: " + "{" + itemToMove.getName() + "}" + " successfully");
} catch (Exception e) {
e.printStackTrace();
}
}
//Check again, if all files has been resolved or not. If yes, delete the old dir "app_Parse".
filesToBeMigrated.clear();
ParseFileUtils.getAllNestedFiles(getOldParseDir(context).getAbsolutePath(), filesToBeMigrated);
if (filesToBeMigrated.isEmpty()) {
try {
ParseFileUtils.deleteDirectory(getOldParseDir(context));
} catch (Exception e) {
e.printStackTrace();
}
}
PLog.v(TAG, "Migration completed.");
}

private String getFileOldDir(Context context, File file) {
//Parse the old sub directory name where the file should be moved (new location) by following the old sub directory name.
String temp = file
.getAbsolutePath()
.replace(
getOldParseDir(context).getAbsolutePath(), "")
.replace("/" + file.getName(), "");
//Before returning the path, replace file name from the last, eg. dir name & file name could be same, as we want to get only dir name.
return replaceLast(temp, file.getName());
}

private void logMigrationStatus(String fileName, String oldPath, String newPath, String status) {
PLog.v(TAG, "Migration for file: " + "{" + fileName + "}" + " from {" + oldPath + "} to {" + newPath + "}, Status: " + status);
}

/*Replace a given string from the last*/
private String replaceLast(String text, String regex) {
return text.replaceFirst("(?s)" + regex + "(?!.*?" + regex + ")", "");
}

private File getOldParseDir(Context context) {
return context.getDir("Parse", Context.MODE_PRIVATE);
}


}
Loading