Bug 886587 - Remove profile migrator, r=gcp, f=rnewman

This commit is contained in:
Mark Capella 2013-07-24 18:50:26 -04:00
parent 4b376ff6ea
commit bcae12953a
12 changed files with 50 additions and 1837 deletions

View File

@ -744,14 +744,6 @@ abstract public class BrowserApp extends GeckoApp
super.onDestroy();
}
@Override
protected void finishProfileMigration() {
// Update about:home with the new information.
updateAboutHomeTopSites();
super.finishProfileMigration();
}
@Override
protected void initializeChrome() {
super.initializeChrome();

View File

@ -167,6 +167,10 @@ abstract public class GeckoApp
static private final String LOCATION_URL = "https://location.services.mozilla.com/v1/submit";
// Delay before running one-time "cleanup" tasks that may be needed
// after a version upgrade.
private static final int CLEANUP_DEFERRAL_SECONDS = 15;
protected RelativeLayout mMainLayout;
protected RelativeLayout mGeckoLayout;
public View getView() { return mGeckoLayout; }
@ -2245,23 +2249,54 @@ abstract public class GeckoApp
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
ProfileMigrator profileMigrator = new ProfileMigrator(app);
profileMigrator.launchDeferredCleanup();
// Do a migration run on the first start after an upgrade.
if (!GeckoApp.sIsUsingCustomProfile &&
!profileMigrator.hasMigrationRun()) {
profileMigrator.launchPlaces(profileDir);
finishProfileMigration();
}
}}
);
Handler handler = new Handler();
handler.postDelayed(new DeferredCleanupTask(), CLEANUP_DEFERRAL_SECONDS * 1000);
}
});
}
}
protected void finishProfileMigration() {
private class DeferredCleanupTask implements Runnable {
// The cleanup-version setting is recorded to avoid repeating the same
// tasks on subsequent startups; CURRENT_CLEANUP_VERSION may be updated
// if we need to do additional cleanup for future Gecko versions.
private static final String CLEANUP_VERSION = "cleanup-version";
private static final int CURRENT_CLEANUP_VERSION = 1;
@Override
public void run() {
long cleanupVersion = getAppSharedPreferences().getInt(CLEANUP_VERSION, 0);
if (cleanupVersion < 1) {
// Reduce device storage footprint by removing .ttf files from
// the res/fonts directory: we no longer need to copy our
// bundled fonts out of the APK in order to use them.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=878674.
File dir = new File("res/fonts");
if (dir.exists() && dir.isDirectory()) {
for (File file : dir.listFiles()) {
if (file.isFile() && file.getName().endsWith(".ttf")) {
Log.i(LOGTAG, "deleting " + file.toString());
file.delete();
}
}
if (!dir.delete()) {
Log.w(LOGTAG, "unable to delete res/fonts directory (not empty?)");
} else {
Log.i(LOGTAG, "res/fonts directory deleted");
}
}
}
// Additional cleanup needed for future versions would go here
if (cleanupVersion != CURRENT_CLEANUP_VERSION) {
SharedPreferences.Editor editor = getAppSharedPreferences().edit();
editor.putInt(CLEANUP_VERSION, CURRENT_CLEANUP_VERSION);
editor.commit();
}
}
}
public PromptService getPromptService() {

View File

@ -162,15 +162,7 @@ public final class GeckoProfile {
}
try {
// Check for old profiles that may need migration.
ProfileMigrator profileMigrator = new ProfileMigrator(mContext);
if (!GeckoApp.sIsUsingCustomProfile &&
!profileMigrator.isProfileMoved()) {
Log.i(LOGTAG, "New installation or update, checking for old profiles.");
profileMigrator.launchMoveProfile();
}
// now check if a profile with this name that already exists
// Check if a profile with this name already exists.
File mozillaDir = ensureMozillaDirectory(mContext);
mDir = findProfileDir(mozillaDir);
if (mDir == null) {

View File

@ -134,7 +134,6 @@ FENNEC_JAVA_FILES = \
PrefsHelper.java \
PrivateDataPreference.java \
PrivateTab.java \
ProfileMigrator.java \
Prompt.java \
PromptInput.java \
PromptService.java \

File diff suppressed because it is too large Load Diff

View File

@ -173,18 +173,6 @@ public class BrowserContract {
public static final String VERSION = "version";
}
public static final class Control {
private Control() {}
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "control");
// These return 1 if done/finished, 0 if not.
// Check if history was completely migrated, do a bunch if it wasn't.
public static final String ENSURE_HISTORY_MIGRATED = "ensure_history_migrated";
// Check if bookmarks were completely migrated, migrate them if not.
public static final String ENSURE_BOOKMARKS_MIGRATED = "ensure_bookmarks_migrated";
}
public static final class Passwords {
private Passwords() {}
public static final Uri CONTENT_URI = Uri.withAppendedPath(PASSWORDS_AUTHORITY_URI, "passwords");

View File

@ -8,12 +8,10 @@ package org.mozilla.gecko.db;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Distribution;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.ProfileMigrator;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserContract.CommonColumns;
import org.mozilla.gecko.db.BrowserContract.Control;
import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
import org.mozilla.gecko.db.BrowserContract.Favicons;
import org.mozilla.gecko.db.BrowserContract.History;
@ -2511,80 +2509,6 @@ public class BrowserProvider extends ContentProvider {
return updated;
}
private Cursor controlQuery(Uri uri,
String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
trace("controlQuery projection = " + projection);
final String[] allFields = {
Control.ENSURE_BOOKMARKS_MIGRATED,
Control.ENSURE_HISTORY_MIGRATED
};
// null projection must return all fields.
if (projection == null) {
projection = allFields;
}
if (selection != null) {
throw new UnsupportedOperationException("No selection in virtual CONTROL queries");
}
File profileDir = GeckoProfile.get(mContext).getDir();
if (uri != null) {
String profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
if (!TextUtils.isEmpty(profile)) {
profileDir = GeckoProfile.get(mContext, profile).getDir();
}
}
MatrixCursor cursor = new MatrixCursor(projection);
MatrixCursor.RowBuilder row = cursor.newRow();
synchronized (this) {
boolean wantBookmarks = false;
boolean wantHistory = false;
for (String key : projection) {
if (key.equals(Control.ENSURE_BOOKMARKS_MIGRATED)) {
wantBookmarks = true;
} else if (key.equals(Control.ENSURE_HISTORY_MIGRATED)) {
wantHistory = true;
}
}
if (wantHistory || wantBookmarks) {
ProfileMigrator migrator = new ProfileMigrator(mContext);
boolean needBookmarks = wantBookmarks && !migrator.areBookmarksMigrated();
boolean needHistory = wantHistory && !migrator.isHistoryMigrated();
if (needBookmarks || needHistory) {
migrator.launchPlaces(profileDir);
needBookmarks = wantBookmarks && !migrator.areBookmarksMigrated();
needHistory = wantHistory && !migrator.isHistoryMigrated();
// Bookmarks are expected to finish at the first run.
if (needBookmarks) {
Log.w(LOGTAG, "Bookmarks migration did not finish.");
}
}
// Now set the results.
for (String key: projection) {
if (key.equals(Control.ENSURE_BOOKMARKS_MIGRATED)) {
row.add(needBookmarks ? 0 : 1);
} else if (key.equals(Control.ENSURE_HISTORY_MIGRATED)) {
row.add(needHistory ? 0 : 1);
}
}
}
}
return cursor;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
@ -2710,15 +2634,6 @@ public class BrowserProvider extends ContentProvider {
break;
}
case CONTROL: {
debug("Query is on control: " + uri);
Cursor controlCursor =
controlQuery(uri, projection, selection, selectionArgs, sortOrder);
return controlCursor;
}
case SEARCH_SUGGEST: {
debug("Query is on search suggest: " + uri);
selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " LIKE ? OR " +

View File

@ -1,131 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.sync.repositories.android;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.db.BrowserContract.Control;
import org.mozilla.gecko.sync.repositories.NoContentProviderException;
import android.content.ContentProviderClient;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
/**
* This class provides an interface to Fenec's control content provider, which
* exposes some of Fennec's internal state, particularly around migrations.
*/
public class FennecControlHelper {
private static final String LOG_TAG = "FennecControlHelper";
protected ContentProviderClient providerClient;
protected final RepoUtils.QueryHelper queryHelper;
public FennecControlHelper(Context context) throws NoContentProviderException {
providerClient = acquireContentProvider(context);
queryHelper = new RepoUtils.QueryHelper(context, Control.CONTENT_URI, LOG_TAG);
}
/**
* Acquire the content provider client.
* <p>
* The caller is responsible for releasing the client.
*
* @param context The application context.
* @return The <code>ContentProviderClient</code>. Never null.
* @throws NoContentProviderException
*/
public static ContentProviderClient acquireContentProvider(final Context context)
throws NoContentProviderException {
final Uri uri = Control.CONTENT_URI;
final ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(uri);
if (client == null) {
throw new NoContentProviderException(uri);
}
return client;
}
/**
* After invoking this method, this instance should be discarded.
*/
public void releaseProviders() {
try {
if (providerClient != null) {
providerClient.release();
}
} catch (Exception e) {
}
providerClient = null;
}
@Override
protected void finalize() throws Throwable {
this.releaseProviders();
super.finalize();
}
private static String[] HISTORY_MIGRATION_COLUMNS = new String[] { Control.ENSURE_HISTORY_MIGRATED };
private static String[] BOOKMARKS_MIGRATION_COLUMNS = new String[] { Control.ENSURE_BOOKMARKS_MIGRATED };
/**
* Pass in a unit array. Returns true if the named column is
* finished migrating; false otherwise.
*
* @param columns an array of a single string, which should be one of the
* permitted control values.
* @return true if the named column is finished migrating; false otherwise.
*/
protected boolean isColumnMigrated(final String[] columns) {
try {
final Cursor cursor = queryHelper.safeQuery(providerClient, ".isColumnMigrated(" + columns[0] + ")",
columns, null, null, null);
try {
if (!cursor.moveToFirst()) {
return false;
}
// This is why we require a unit array.
return cursor.getInt(0) > 0;
} finally {
cursor.close();
}
} catch (Exception e) {
Logger.warn(LOG_TAG, "Caught exception checking if Fennec has migrated column " + columns[0] + ".", e);
return false;
}
}
/**
* @param context the context to use when querying.
* @param columns an array of a single string, which should be one of the
* permitted control values.
* @return true if the named column is finished migrating; false otherwise.
*/
protected static boolean isColumnMigrated(Context context, String[] columns) {
if (context == null) {
return false;
}
try {
final FennecControlHelper control = new FennecControlHelper(context);
try {
return control.isColumnMigrated(columns);
} finally {
control.releaseProviders();
}
} catch (Exception e) {
Logger.warn(LOG_TAG, "Caught exception checking if Fennec has migrated column " + columns[0] + ".", e);
return false;
}
}
public static boolean isHistoryMigrated(Context context) {
return isColumnMigrated(context, HISTORY_MIGRATION_COLUMNS);
}
public static boolean areBookmarksMigrated(Context context) {
return isColumnMigrated(context, BOOKMARKS_MIGRATION_COLUMNS);
}
}

View File

@ -6,7 +6,6 @@
[testBookmark]
[testBookmarklets]
[testJNI]
[testMigration]
[testLoad]
[testNewTab]
[testOrderedBroadcast]

View File

@ -47,9 +47,6 @@
#[testLoad]
# fails on gs2, nexus one, lg revolution, droid pro, nexus s
[testMigration]
# fails on lg-revolution random android.database.sqlite.SQLiteDiskIOException
[testNewTab]
# fails on nexus s random crash [@ libpvrANDROID_WSEGL.so + 0x73c]

View File

@ -1,350 +0,0 @@
#filter substitution
package @ANDROID_PACKAGE_NAME@.tests;
import android.content.ContentResolver;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Tests the profile migration. Unpacks an old database, moves
* it to the profile, then call the BrowserProvider Control URI
* to launch a migration and check the results.
*/
public class testMigration extends ContentProviderTest {
// Big files are zipped to allow us to manually extract them,
// the APK extractor will fail >1Mb.
private static final String ASSET_SUFFIX = ".zip";
private static final String DB_NAME = "places.sqlite";
@Override
protected int getTestType() {
return TEST_MOCHITEST;
}
private File extractAsset(String dbName) {
File oldDbLocation = null;
boolean foundFile = false;
String fullAssetName = dbName + ASSET_SUFFIX;
AssetManager assets = getAssetManager();
try {
String[] assetList = assets.list("");
String assetString = "";
for (String file: assetList) {
assetString = assetString.concat(file);
assetString = assetString.concat(" ");
if (file.equals(fullAssetName)) {
foundFile = true;
}
}
mAsserter.is(foundFile, true, fullAssetName + " found in assets: " + assetString);
} catch (IOException e) {
String stackTrace = Log.getStackTraceString(e);
mAsserter.is(false, true, "Error getting asset dir: " + stackTrace);
}
// Extract the old places database from assets
// and write it out in the profile directory
try {
InputStream profData = assets.open(fullAssetName);
File profile = new File(mProfile);
oldDbLocation = new File(profile, dbName);
OutputStream out = new FileOutputStream(oldDbLocation);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(profData));
try {
ZipEntry ze;
while ((ze = zis.getNextEntry()) != null) {
byte[] buffer = new byte[1024];
int count;
while ((count = zis.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
String fileName = ze.getName();
mAsserter.is(fileName, DB_NAME, "Filename match: " + fileName);
}
} finally {
zis.close();
}
profData.close();
out.flush();
out.close();
} catch (IOException e) {
String stackTrace = Log.getStackTraceString(e);
mAsserter.is(false, true, "Error getting profile data: " + stackTrace);
}
return oldDbLocation;
}
public void testMigration() {
Context context = (Context)getActivity();
File oldDbLocation = extractAsset(DB_NAME);
mAsserter.is(oldDbLocation.exists(), true, "OK old file exists: "
+ oldDbLocation.toString());
// Set up BrowserDB
try {
Class browserDBClass =
mClassLoader.loadClass("org.mozilla.gecko.db.BrowserDB");
Method initializeMethod =
browserDBClass.getDeclaredMethod("initialize", String.class);
initializeMethod.invoke(null, "default");
} catch (Exception ex) {
mAsserter.is(true, false, "Error setting up BrowserDB: "
+ ex.getMessage());
return;
}
// Use reflection to look up the ProfileMigrator things we need
// access to (constructor, launch), as well as BrowserContract.Control.
Method launchPlacesTest;
Constructor constructor;
Class pmClass;
try {
pmClass = mClassLoader.loadClass("org.mozilla.gecko.ProfileMigrator");
launchPlacesTest = pmClass.getMethod("launchPlacesTest", File.class);
constructor = pmClass.getConstructor(Context.class, ContentResolver.class);
} catch(ClassNotFoundException ex) {
mAsserter.is(false, true, "Error getting class: " + ex.getMessage());
return;
} catch (java.lang.NoSuchMethodException ex) {
mAsserter.is(true, false, "Unable to find method: " + ex.getMessage());
return;
}
Object pm = null;
try {
// Construct ProfileMigrator object
pm = constructor.newInstance(context, mResolver);
} catch (Exception ex) {
mAsserter.is(true, false, "Error instantiating ProfileMigrator instance: "
+ ex.getMessage());
return;
}
// Reset history entries from previous tests
clearHistory();
// Launch the Profile Migration
try {
launchPlacesTest.invoke(pm, new File(mProfile));
} catch (Exception ex) {
String stackTrace = Log.getStackTraceString(ex);
mAsserter.is(true, false, "Unable to invoke launchPlacesTest:" + stackTrace);
return;
}
// Run the tests to see if that worked
runTestViaContentProvider();
runTestViaBrowserDB();
mAsserter.is(oldDbLocation.exists(), false, "OK old file gone now");
}
private void clearHistory() {
Method clearHistory = null;
try {
Class browserDB =
mClassLoader.loadClass("org.mozilla.gecko.db.BrowserDB");
clearHistory =
browserDB.getMethod("clearHistory", ContentResolver.class);
} catch (java.lang.ClassNotFoundException ex) {
mAsserter.is(true, false, "Unable to find class");
return;
} catch (java.lang.NoSuchMethodException ex) {
mAsserter.is(true, false, "Unable to find method");
return;
}
try {
clearHistory.invoke(null, mResolver);
} catch (Exception ex) {
String stackTrace = Log.getStackTraceString(ex);
mAsserter.is(true, false, "Exception clearing history:" + stackTrace);
}
}
// stolen from testBookmarks
private void runTestViaBrowserDB() {
Method isBookmarked = null;
Method getAllVisitedHistory = null;
Constructor constructor = null;
try {
Class localBrowserDB =
mClassLoader.loadClass("org.mozilla.gecko.db.LocalBrowserDB");
isBookmarked =
localBrowserDB.getMethod("isBookmark", ContentResolver.class, String.class);
getAllVisitedHistory =
localBrowserDB.getMethod("getAllVisitedHistory", ContentResolver.class);
constructor = localBrowserDB.getConstructor(String.class);
} catch (java.lang.ClassNotFoundException ex) {
mAsserter.is(true, false, "Unable to find class");
return;
} catch (java.lang.NoSuchMethodException ex) {
mAsserter.is(true, false, "Unable to find method:" + ex.getMessage());
return;
}
Object db = null;
try {
String defaultProfile = "default";
db = constructor.newInstance(defaultProfile);
} catch (Exception ex) {
mAsserter.is(true, false, "Error instantiating LocalBrowserDB instance: "
+ ex.getMessage());
return;
}
final String[] knownBookmarks = new String[] {
"http://www.androidpolice.com/",
"https://developer.mozilla.org/En/Mozilla_Coding_Style_Guide",
"http://planet.mozilla.org/",
"http://www.crockford.com/",
"https://wiki.mozilla.org/Mobile/Fennec/Android"
};
for (String url: knownBookmarks) {
try {
// Check for some bookmarks we know must exist
boolean isbookmark =
(Boolean)isBookmarked.invoke(db,
mResolver,
url);
mAsserter.is(isbookmark, true, "Expected page is bookmarked: " + url);
} catch (Exception ex) {
mAsserter.is(true, false, "Exception checking bookmark existence");
ex.printStackTrace();
return;
}
}
// Check the amount of history, this should match the following on the
// database in assets:
// SELECT COUNT(DISTINCT(url)) FROM moz_historyvisits
// JOIN moz_places ON moz_historyvisits.place_id = moz_places.id
final int PLACES_KNOWN_HISTORY_URLS = 184;
try {
Cursor cursor =
(Cursor)getAllVisitedHistory.invoke(db,
mResolver);
int historyCount = cursor.getCount();
cursor.close();
mAsserter.is(historyCount, PLACES_KNOWN_HISTORY_URLS,
"History count " + historyCount +
", expected was " + PLACES_KNOWN_HISTORY_URLS);
} catch (Exception ex) {
mAsserter.is(true, false, "Exception checking history count");
ex.printStackTrace();
}
}
private void runTestViaContentProvider() {
String ensureHistory;
String ensureBookmarks;
String urlField;
String visitsField;
Uri controlUri;
Uri historyUri;
try {
Class browserContract =
mClassLoader.loadClass("org.mozilla.gecko.db.BrowserContract");
Class browserContractControl =
mClassLoader.loadClass("org.mozilla.gecko.db.BrowserContract$Control");
Class browserContractHistory =
mClassLoader.loadClass("org.mozilla.gecko.db.BrowserContract$History");
Class browserContractUrl =
mClassLoader.loadClass("org.mozilla.gecko.db.BrowserContract$URLColumns");
Class browserContractHistoryColumns =
mClassLoader.loadClass("org.mozilla.gecko.db.BrowserContract$HistoryColumns");
controlUri = (Uri)browserContractControl.getField("CONTENT_URI").get(null);
historyUri = (Uri)browserContractHistory.getField("CONTENT_URI").get(null);
urlField = (String)browserContractUrl.getField("URL").get(null);
visitsField = (String)browserContractHistoryColumns.getField("VISITS").get(null);
String profilePath = (String)browserContract.getField("PARAM_PROFILE_PATH").get(null);
Uri.Builder builder = controlUri.buildUpon();
controlUri = builder.build();
ensureHistory =
(String)browserContractControl.getField("ENSURE_HISTORY_MIGRATED").get(null);
ensureBookmarks =
(String)browserContractControl.getField("ENSURE_BOOKMARKS_MIGRATED").get(null);
} catch (Exception ex) {
mAsserter.is(true, false, "Reflection error getting BrowserContract classes and Uri's");
ex.printStackTrace();
return;
}
Cursor c = mResolver.query(controlUri,
new String[] { ensureHistory,
ensureBookmarks },
null,
null,
null);
int historyMigrated = 0;
int bookmarksMigrated = 0;
if (c.moveToFirst()) {
historyMigrated = c.getInt(0);
bookmarksMigrated = c.getInt(1);
}
c.close();
mAsserter.is(historyMigrated, 1, "History migrated");
mAsserter.is(bookmarksMigrated, 1, "Bookmarks migrated");
// Check whether visit counts are as expected. The test profile
// has visited reddit 4 times so we expect to find that in our
// own database too now.
c = mResolver.query(historyUri,
new String[] { visitsField },
urlField + " = ?",
new String[] { "http://www.reddit.com/" },
null);
mAsserter.is(c.moveToFirst(), true, "Expected URL found");
int visits = c.getInt(0);
c.close();
mAsserter.is(visits, 4, "Visit count of " + visits + " equals expected 4");
}
@Override
public void setUp() throws Exception {
super.setUp("org.mozilla.gecko.db.BrowserProvider", "AUTHORITY");
}
@Override
public void tearDown() throws Exception {
// remove the database file
File profile = new File(mProfile);
File db = new File(profile, DB_NAME);
if (db.delete()) {
mAsserter.dumpLog("tearDown deleted "+db.toString());
} else {
mAsserter.dumpLog("tearDown did not delete "+db.toString());
}
super.tearDown();
}
}