gecko/mobile/android/base/GeckoProfile.java
William Lachance f2e2a0c3e4 Bug 792212 - Don't attempt to migrate profiles if using a custom profile;r=gbrown
It seems that doing so triggers a race condition which can result in the fennec
reftests failing, if the shared preferences associated with profile migration or
the gecko application is not correctly initialized. This is more or less a band
aid solution, but it addresses the problem until we can find the root cause.
2012-09-28 22:52:11 -04:00

382 lines
13 KiB
Java

/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
*
* 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/.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko;
import org.mozilla.gecko.util.INIParser;
import org.mozilla.gecko.util.INISection;
import android.content.Context;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
public final class GeckoProfile {
private static final String LOGTAG = "GeckoProfile";
private static HashMap<String, GeckoProfile> sProfileCache = new HashMap<String, GeckoProfile>();
private final Context mContext;
private final String mName;
private File mMozDir;
private File mDir;
// this short timeout is a temporary fix until bug 735399 is implemented
private static final long SESSION_TIMEOUT = 30 * 1000; // 30 seconds
static private INIParser getProfilesINI(Context context) {
File filesDir = context.getFilesDir();
File mozillaDir = new File(filesDir, "mozilla");
File profilesIni = new File(mozillaDir, "profiles.ini");
return new INIParser(profilesIni);
}
public static GeckoProfile get(Context context) {
if (context instanceof GeckoApp)
return get(context, ((GeckoApp)context).getDefaultProfileName());
return get(context, "");
}
public static GeckoProfile get(Context context, String profileName) {
return get(context, profileName, null);
}
public static GeckoProfile get(Context context, String profileName, String profilePath) {
if (context == null) {
throw new IllegalArgumentException("context must be non-null");
}
// if no profile was passed in, look for the default profile listed in profiles.ini
// if that doesn't exist, look for a profile called 'default'
if (TextUtils.isEmpty(profileName) && TextUtils.isEmpty(profilePath)) {
profileName = "default";
INIParser parser = getProfilesINI(context);
String profile = "";
boolean foundDefault = false;
for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
INISection section = e.nextElement();
if (section.getIntProperty("Default") == 1) {
profile = section.getStringProperty("Name");
foundDefault = true;
}
}
if (foundDefault)
profileName = profile;
}
// actually try to look up the profile
synchronized (sProfileCache) {
GeckoProfile profile = sProfileCache.get(profileName);
if (profile == null) {
profile = new GeckoProfile(context, profileName, profilePath);
sProfileCache.put(profileName, profile);
} else {
profile.setDir(profilePath);
}
return profile;
}
}
public static File ensureMozillaDirectory(Context context) throws IOException {
synchronized (context) {
File filesDir = context.getFilesDir();
File mozDir = new File(filesDir, "mozilla");
if (! mozDir.exists()) {
if (! mozDir.mkdirs()) {
throw new IOException("Unable to create mozilla directory at " + mozDir.getAbsolutePath());
}
}
return mozDir;
}
}
public static boolean removeProfile(Context context, String profileName) {
return new GeckoProfile(context, profileName).remove();
}
private GeckoProfile(Context context, String profileName) {
mContext = context;
mName = profileName;
}
private GeckoProfile(Context context, String profileName, String profilePath) {
mContext = context;
mName = profileName;
setDir(profilePath);
}
private void setDir(String profilePath) {
if (!TextUtils.isEmpty(profilePath)) {
File dir = new File(profilePath);
if (dir.exists() && dir.isDirectory()) {
if (mDir != null) {
Log.i(LOGTAG, "profile dir changed from "+mDir+" to "+dir);
}
mDir = dir;
} else {
Log.w(LOGTAG, "requested profile directory missing: "+profilePath);
}
}
}
public String getName() {
return mName;
}
public synchronized File getDir() {
if (mDir != null) {
return mDir;
}
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
File mozillaDir = ensureMozillaDirectory(mContext);
mDir = findProfileDir(mozillaDir);
if (mDir == null) {
// otherwise create it
mDir = createProfileDir(mozillaDir);
} else {
Log.d(LOGTAG, "Found profile dir: " + mDir.getAbsolutePath());
}
} catch (IOException ioe) {
Log.e(LOGTAG, "Error getting profile dir", ioe);
}
return mDir;
}
public File getFile(String aFile) {
File f = getDir();
if (f == null)
return null;
return new File(f, aFile);
}
public File getFilesDir() {
return mContext.getFilesDir();
}
public boolean shouldRestoreSession() {
Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - start check sessionstore.js exists");
File dir = getDir();
if (dir == null)
return false;
File sessionFile = new File(dir, "sessionstore.js");
if (!sessionFile.exists())
return false;
boolean shouldRestore = (System.currentTimeMillis() - sessionFile.lastModified() < SESSION_TIMEOUT);
Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - finish check sessionstore.js exists");
return shouldRestore;
}
public String readSessionFile(boolean geckoReady) {
File dir = getDir();
if (dir == null) {
return null;
}
File sessionFile = null;
if (! geckoReady) {
// we might have crashed, in which case sessionstore.js has tabs from last time
sessionFile = new File(dir, "sessionstore.js");
if (! sessionFile.exists()) {
sessionFile = null;
}
}
if (sessionFile == null) {
// either we did not crash, so previous session was moved to sessionstore.bak on quit,
// or sessionstore init has occurred, so previous session will always
// be in sessionstore.bak
sessionFile = new File(dir, "sessionstore.bak");
// no need to check if the session file exists here; readFile will throw
// an IOException if it does not
}
try {
return readFile(sessionFile);
} catch (IOException ioe) {
Log.i(LOGTAG, "Unable to read session file " + sessionFile.getAbsolutePath());
return null;
}
}
public String readFile(String filename) throws IOException {
File dir = getDir();
if (dir == null) {
throw new IOException("No profile directory found");
}
File target = new File(dir, filename);
return readFile(target);
}
private String readFile(File target) throws IOException {
FileReader fr = new FileReader(target);
try {
StringBuffer sb = new StringBuffer();
char[] buf = new char[8192];
int read = fr.read(buf);
while (read >= 0) {
sb.append(buf, 0, read);
read = fr.read(buf);
}
return sb.toString();
} finally {
fr.close();
}
}
private boolean remove() {
try {
File mozillaDir = ensureMozillaDirectory(mContext);
mDir = findProfileDir(mozillaDir);
if (mDir == null)
return false;
INIParser parser = getProfilesINI(mContext);
Hashtable<String, INISection> sections = parser.getSections();
for (Enumeration<INISection> e = sections.elements(); e.hasMoreElements();) {
INISection section = e.nextElement();
String name = section.getStringProperty("Name");
if (name == null || !name.equals(mName))
continue;
if (section.getName().startsWith("Profile")) {
// ok, we have stupid Profile#-named things. Rename backwards.
try {
int sectionNumber = Integer.parseInt(section.getName().substring("Profile".length()));
String curSection = "Profile" + sectionNumber;
String nextSection = "Profile" + (sectionNumber+1);
sections.remove(curSection);
while (sections.containsKey(nextSection)) {
parser.renameSection(nextSection, curSection);
sectionNumber++;
curSection = nextSection;
nextSection = "Profile" + (sectionNumber+1);
}
} catch (NumberFormatException nex) {
// uhm, malformed Profile thing; we can't do much.
Log.e(LOGTAG, "Malformed section name in profiles.ini: " + section.getName());
return false;
}
} else {
// this really shouldn't be the case, but handle it anyway
parser.removeSection(mName);
return true;
}
}
parser.write();
return true;
} catch (IOException ex) {
Log.w(LOGTAG, "Failed to remove profile " + mName + ":\n" + ex);
return false;
}
}
private File findProfileDir(File mozillaDir) {
// Open profiles.ini to find the correct path
INIParser parser = getProfilesINI(mContext);
for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
INISection section = e.nextElement();
String name = section.getStringProperty("Name");
if (name != null && name.equals(mName)) {
if (section.getIntProperty("IsRelative") == 1) {
return new File(mozillaDir, section.getStringProperty("Path"));
}
return new File(section.getStringProperty("Path"));
}
}
return null;
}
private static String saltProfileName(String name) {
String allowedChars = "abcdefghijklmnopqrstuvwxyz0123456789";
StringBuffer salt = new StringBuffer(16);
for (int i = 0; i < 8; i++) {
salt.append(allowedChars.charAt((int)(Math.random() * allowedChars.length())));
}
salt.append('.');
salt.append(name);
return salt.toString();
}
private File createProfileDir(File mozillaDir) throws IOException {
INIParser parser = getProfilesINI(mContext);
// Salt the name of our requested profile
String saltedName = saltProfileName(mName);
File profileDir = new File(mozillaDir, saltedName);
while (profileDir.exists()) {
saltedName = saltProfileName(mName);
profileDir = new File(mozillaDir, saltedName);
}
// Attempt to create the salted profile dir
if (! profileDir.mkdirs()) {
throw new IOException("Unable to create profile at " + profileDir.getAbsolutePath());
}
Log.d(LOGTAG, "Created new profile dir at " + profileDir.getAbsolutePath());
// Now update profiles.ini
// If this is the first time its created, we also add a General section
// look for the first profile number that isn't taken yet
int profileNum = 0;
while (parser.getSection("Profile" + profileNum) != null) {
profileNum++;
}
INISection profileSection = new INISection("Profile" + profileNum);
profileSection.setProperty("Name", mName);
profileSection.setProperty("IsRelative", 1);
profileSection.setProperty("Path", saltedName);
if (parser.getSection("General") == null) {
INISection generalSection = new INISection("General");
generalSection.setProperty("StartWithLastProfile", 1);
parser.addSection(generalSection);
// only set as default if this is the first profile we're creating
profileSection.setProperty("Default", 1);
}
parser.addSection(profileSection);
parser.write();
return profileDir;
}
}