Bug 715307 - Read and write profiles.ini for Android. r=mfinkle

This commit is contained in:
Wes Johnston 2012-06-15 09:02:11 -07:00
parent 7122fce81e
commit 096e6e91e8
11 changed files with 383 additions and 36 deletions

View File

@ -1722,6 +1722,8 @@ abstract public class GeckoApp
}
}
BrowserDB.initialize(getProfile().getName());
if (ACTION_UPDATE.equals(action) || args != null && args.contains("-alert update-app")) {
Log.i(LOGTAG,"onCreate: Update request");
checkAndLaunchUpdate();

View File

@ -9,12 +9,15 @@
package org.mozilla.gecko;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Enumeration;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
@ -34,19 +37,44 @@ public final class GeckoProfile {
// 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) {
return get(context, null);
return get(context, "");
}
public static GeckoProfile get(Context context, String profileName) {
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)) {
// XXX: TO-DO read profiles.ini to get the default profile. bug 715307
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) {
@ -75,6 +103,10 @@ public final class GeckoProfile {
mName = profileName;
}
public String getName() {
return mName;
}
public synchronized File getDir() {
if (mDir != null) {
return mDir;
@ -88,9 +120,11 @@ public final class GeckoProfile {
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());
@ -177,16 +211,20 @@ public final class GeckoProfile {
}
private File findProfileDir(File mozillaDir) {
String suffix = '.' + mName;
File[] candidates = mozillaDir.listFiles();
if (candidates == null) {
return null;
}
for (File f : candidates) {
if (f.isDirectory() && f.getName().endsWith(suffix)) {
return f;
// 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;
}
@ -202,14 +240,9 @@ public final class GeckoProfile {
}
private File createProfileDir(File mozillaDir) throws IOException {
// XXX: TO-DO If we already have an ini file, we should append the
// new profile information to it. For now we just throw an exception.
// see bug 715391
File profileIniFile = new File(mozillaDir, "profiles.ini");
if (profileIniFile.exists()) {
throw new IOException("Can't create new profiles");
}
INIParser parser = getProfilesINI(mContext);
// Salt the name of our requested profile
String saltedName = saltProfileName(mName);
File profileDir = new File(mozillaDir, saltedName);
while (profileDir.exists()) {
@ -217,25 +250,38 @@ public final class GeckoProfile {
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());
FileWriter out = new FileWriter(profileIniFile, true);
try {
out.write("[General]\n" +
"StartWithLastProfile=1\n" +
"\n" +
"[Profile0]\n" +
"Name=" + mName + "\n" +
"IsRelative=1\n" +
"Path=" + saltedName + "\n" +
"Default=1\n");
} finally {
out.close();
// 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
Log.i(LOGTAG, "WESJ - SET DEFAULT");
profileSection.setProperty("Default", 1);
}
parser.addSection(profileSection);
parser.write();
return profileDir;
}
}

View File

@ -0,0 +1,163 @@
/* 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;
import android.text.TextUtils;
import android.util.Log;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
public class INIParser extends INISection {
// default file to read and write to
private File mFile = null;
// List of sections in the current iniFile. null if the file has not been parsed yet
private Hashtable<String, INISection> mSections = null;
// create a parser. The file will not be read until you attempt to
// access sections or properties inside it. At that point its read synchronously
public INIParser(File iniFile) {
super("");
mFile = iniFile;
}
// write ini data to the default file. Will overwrite anything current inside
public void write() {
writeTo(mFile);
}
// write to the specified file. Will overwrite anything current inside
public void writeTo(File f) {
if (f == null)
return;
FileWriter outputStream = null;
try {
outputStream = new FileWriter(f);
} catch (IOException e1) {
e1.printStackTrace();
}
BufferedWriter writer = new BufferedWriter(outputStream);
try {
write(writer);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void write(BufferedWriter writer) throws IOException {
super.write(writer);
if (mSections != null) {
for (Enumeration<INISection> e = mSections.elements(); e.hasMoreElements();) {
INISection section = e.nextElement();
section.write(writer);
writer.newLine();
}
}
}
// return all of the sections inside this file
public Hashtable<String, INISection> getSections() {
if (mSections == null) {
try {
parse();
} catch (IOException e) {
debug("Error parsing: " + e);
}
}
return mSections;
}
// parse the default file
protected void parse() throws IOException {
super.parse();
parse(mFile);
}
// parse a passed in file
private void parse(File f) throws IOException {
// Set up internal data members
mSections = new Hashtable<String, INISection>();
if (f == null || !f.exists())
return;
FileReader inputStream = null;
try {
inputStream = new FileReader(f);
} catch (FileNotFoundException e1) {
// If the file doesn't exist. Just return;
return;
}
BufferedReader buf = new BufferedReader(inputStream);
String line = null; // current line of text we are parsing
INISection currentSection = null; // section we are currently parsing
while ((line = buf.readLine()) != null) {
if (line != null)
line = line.trim();
// blank line or a comment. ignore it
if (line == null || line.length() == 0 || line.charAt(0) == ';') {
debug("Ignore line: " + line);
} else if (line.charAt(0) == '[') {
debug("Parse as section: " + line);
currentSection = new INISection(line.substring(1, line.length()-1));
mSections.put(currentSection.getName(), currentSection);
} else {
debug("Parse as property: " + line);
String[] pieces = line.split("=");
if (pieces.length != 2)
continue;
String key = pieces[0].trim();
String value = pieces[1].trim();
if (currentSection != null) {
currentSection.setProperty(key, value);
} else {
mProperties.put(key, value);
}
}
}
buf.close();
}
// add a section to the file
public void addSection(INISection sect) {
// ensure that we have parsed the file
getSections();
mSections.put(sect.getName(), sect);
}
// get a section from the file. will return null if the section doesn't exist
public INISection getSection(String key) {
// ensure that we have parsed the file
getSections();
return mSections.get(key);
}
// remove an entire section from the file
public void removeSection(String name) {
// ensure that we have parsed the file
getSections();
mSections.remove(name);
}
}

View File

@ -0,0 +1,128 @@
/* 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;
import android.text.TextUtils;
import android.util.Log;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
class INISection {
private static final String LOGTAG = "INIParser";
// default file to read and write to
private String mName = null;
public String getName() { return mName; }
// show or hide debug logging
private boolean mDebug = false;
// Global properties that aren't inside a section in the file
protected Hashtable<String, Object> mProperties = null;
// create a parser. The file will not be read until you attempt to
// access sections or properties inside it. At that point its read synchronously
public INISection(String name) {
mName = name;
}
// log a debug string to the console
protected void debug(String msg) {
if (mDebug) {
Log.i(LOGTAG, msg);
}
}
// get a global property out of the hash table. will return null if the property doesn't exist
public Object getProperty(String key) {
getProperties(); // ensure that we have parsed the file
return mProperties.get(key);
}
// get a global property out of the hash table. will return null if the property doesn't exist
public int getIntProperty(String key) {
Object val = getProperty(key);
if (val == null)
return -1;
Integer i = new Integer(val.toString());
return i.intValue();
}
// get a global property out of the hash table. will return null if the property doesn't exist
public String getStringProperty(String key) {
Object val = getProperty(key);
if (val == null)
return null;
return val.toString();
}
// get a hashtable of all the global properties in this file
public Hashtable<String, Object> getProperties() {
if (mProperties == null) {
try {
parse();
} catch (IOException e) {
debug("Error parsing: " + e);
}
}
return mProperties;
}
// do nothing for generic sections
protected void parse() throws IOException {
mProperties = new Hashtable<String, Object>();
}
// set a property. Will erase the property if value = null
public void setProperty(String key, Object value) {
getProperties(); // ensure that we have parsed the file
if (value == null)
removeProperty(key);
else
mProperties.put(key.trim(), value);
}
// remove a property
public void removeProperty(String name) {
// ensure that we have parsed the file
getProperties();
mProperties.remove(name);
}
public void write(BufferedWriter writer) throws IOException {
if (!TextUtils.isEmpty(mName)) {
writer.write("[" + mName + "]");
writer.newLine();
}
if (mProperties != null) {
for (Enumeration<String> e = mProperties.keys(); e.hasMoreElements();) {
String key = e.nextElement();
writeProperty(writer, key, mProperties.get(key));
}
}
writer.newLine();
}
// Helper function to write out a property
private void writeProperty(BufferedWriter writer, String key, Object value) {
try {
writer.write(key + "=" + value);
writer.newLine();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -63,6 +63,8 @@ FENNEC_JAVA_FILES = \
GeckoThread.java \
GlobalHistory.java \
GeckoViewsFactory.java \
INIParser.java \
INISection.java \
LinkPreference.java \
LinkTextView.java \
MenuItemActionBar.java \

View File

@ -21,7 +21,6 @@ public class BrowserContract {
public static final String TABS_AUTHORITY = "@ANDROID_PACKAGE_NAME@.db.tabs";
public static final Uri TABS_AUTHORITY_URI = Uri.parse("content://" + TABS_AUTHORITY);
public static final String DEFAULT_PROFILE = "default";
public static final String PARAM_PROFILE = "profile";
public static final String PARAM_PROFILE_PATH = "profilePath";
public static final String PARAM_LIMIT = "limit";

View File

@ -10,6 +10,8 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.drawable.BitmapDrawable;
import org.mozilla.gecko.GeckoProfile;
public class BrowserDB {
public static String ABOUT_PAGES_URL_FILTER = "about:%";
@ -23,7 +25,7 @@ public class BrowserDB {
public static String KEYWORD = "keyword";
}
private static BrowserDBIface sDb;
private static BrowserDBIface sDb = null;
public interface BrowserDBIface {
public void invalidateCachedState();
@ -78,7 +80,11 @@ public class BrowserDB {
static {
// Forcing local DB no option to switch to Android DB for now
sDb = new LocalBrowserDB(BrowserContract.DEFAULT_PROFILE);
sDb = null;
}
public static void initialize(String profile) {
sDb = new LocalBrowserDB(profile);
}
public static void invalidateCachedState() {

View File

@ -1036,7 +1036,7 @@ public class BrowserProvider extends ContentProvider {
// Always fallback to default profile if none has been provided.
if (TextUtils.isEmpty(profile)) {
profile = BrowserContract.DEFAULT_PROFILE;
profile = GeckoProfile.get(mContext).getName();
}
DatabaseHelper dbHelper;

View File

@ -153,7 +153,8 @@ public abstract class GeckoProvider extends ContentProvider {
private SQLiteBridge getDatabaseForProfile(String profile) {
if (TextUtils.isEmpty(profile)) {
profile = BrowserContract.DEFAULT_PROFILE;
profile = GeckoProfile.get(mContext).getName();
Log.d(mLogTag, "No profile provided, using '" + profile + "'");
}
SQLiteBridge db = null;

View File

@ -198,7 +198,7 @@ public class TabsProvider extends ContentProvider {
// Always fallback to default profile if none has been provided.
if (TextUtils.isEmpty(profile)) {
profile = BrowserContract.DEFAULT_PROFILE;
profile = GeckoProfile.get(getContext()).getName();
}
DatabaseHelper dbHelper;

View File

@ -224,7 +224,7 @@ abstract class ContentProviderTest extends AndroidTestCase {
Method getDatabasePath =
mProviderClass.getDeclaredMethod("getDatabasePath", String.class, boolean.class);
String defaultProfile = (String) mProviderContract.getField("DEFAULT_PROFILE").get(null);
String defaultProfile = "default";
databaseName = (String) getDatabasePath.invoke(mProvider, defaultProfile, true /* is test */);
} catch (Exception e) {}