mobile/android/base/GeckoProfileDirectories.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/GeckoProfileDirectories.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,229 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko;
     1.9 +
    1.10 +import java.io.File;
    1.11 +import java.util.Enumeration;
    1.12 +import java.util.HashMap;
    1.13 +import java.util.Map;
    1.14 +
    1.15 +import org.mozilla.gecko.util.INIParser;
    1.16 +import org.mozilla.gecko.util.INISection;
    1.17 +
    1.18 +import android.content.Context;
    1.19 +
    1.20 +/**
    1.21 + * <code>GeckoProfileDirectories</code> manages access to mappings from profile
    1.22 + * names to salted profile directory paths, as well as the default profile name.
    1.23 + *
    1.24 + * This class will eventually come to encapsulate the remaining logic embedded
    1.25 + * in profiles.ini; for now it's a read-only wrapper.
    1.26 + */
    1.27 +public class GeckoProfileDirectories {
    1.28 +    @SuppressWarnings("serial")
    1.29 +    public static class NoMozillaDirectoryException extends Exception {
    1.30 +        public NoMozillaDirectoryException(Throwable cause) {
    1.31 +            super(cause);
    1.32 +        }
    1.33 +
    1.34 +        public NoMozillaDirectoryException(String reason) {
    1.35 +            super(reason);
    1.36 +        }
    1.37 +
    1.38 +        public NoMozillaDirectoryException(String reason, Throwable cause) {
    1.39 +            super(reason, cause);
    1.40 +        }
    1.41 +    }
    1.42 +
    1.43 +    @SuppressWarnings("serial")
    1.44 +    public static class NoSuchProfileException extends Exception {
    1.45 +        public NoSuchProfileException(String detailMessage, Throwable cause) {
    1.46 +            super(detailMessage, cause);
    1.47 +        }
    1.48 +
    1.49 +        public NoSuchProfileException(String detailMessage) {
    1.50 +            super(detailMessage);
    1.51 +        }
    1.52 +    }
    1.53 +
    1.54 +    private interface INISectionPredicate {
    1.55 +        public boolean matches(INISection section);
    1.56 +    }
    1.57 +
    1.58 +    private static final String MOZILLA_DIR_NAME = "mozilla";
    1.59 +
    1.60 +    /**
    1.61 +     * Returns true if the supplied profile entry represents the default profile.
    1.62 +     */
    1.63 +    private static INISectionPredicate sectionIsDefault = new INISectionPredicate() {
    1.64 +        @Override
    1.65 +        public boolean matches(INISection section) {
    1.66 +            return section.getIntProperty("Default") == 1;
    1.67 +        }
    1.68 +    };
    1.69 +
    1.70 +    /**
    1.71 +     * Returns true if the supplied profile entry has a 'Name' field.
    1.72 +     */
    1.73 +    private static INISectionPredicate sectionHasName = new INISectionPredicate() {
    1.74 +        @Override
    1.75 +        public boolean matches(INISection section) {
    1.76 +            final String name = section.getStringProperty("Name");
    1.77 +            return name != null;
    1.78 +        }
    1.79 +    };
    1.80 +
    1.81 +    /**
    1.82 +     * Package-scoped because GeckoProfile needs to dig into this in order to do writes.
    1.83 +     * This will be fixed in Bug 975212.
    1.84 +     */
    1.85 +    static INIParser getProfilesINI(File mozillaDir) {
    1.86 +        return new INIParser(new File(mozillaDir, "profiles.ini"));
    1.87 +    }
    1.88 +
    1.89 +    /**
    1.90 +     * Utility method to compute a salted profile name: eight random alphanumeric
    1.91 +     * characters, followed by a period, followed by the profile name.
    1.92 +     */
    1.93 +    public static String saltProfileName(final String name) {
    1.94 +        if (name == null) {
    1.95 +            throw new IllegalArgumentException("Cannot salt null profile name.");
    1.96 +        }
    1.97 +
    1.98 +        final String allowedChars = "abcdefghijklmnopqrstuvwxyz0123456789";
    1.99 +        final int scale = allowedChars.length();
   1.100 +        final int saltSize = 8;
   1.101 +
   1.102 +        final StringBuilder saltBuilder = new StringBuilder(saltSize + 1 + name.length());
   1.103 +        for (int i = 0; i < saltSize; i++) {
   1.104 +            saltBuilder.append(allowedChars.charAt((int)(Math.random() * scale)));
   1.105 +        }
   1.106 +        saltBuilder.append('.');
   1.107 +        saltBuilder.append(name);
   1.108 +        return saltBuilder.toString();
   1.109 +    }
   1.110 +
   1.111 +    /**
   1.112 +     * Return the Mozilla directory within the files directory of the provided
   1.113 +     * context. This should always be the same within a running application.
   1.114 +     *
   1.115 +     * This method is package-scoped so that new {@link GeckoProfile} instances can
   1.116 +     * contextualize themselves.
   1.117 +     *
   1.118 +     * @return a new File object for the Mozilla directory.
   1.119 +     * @throws NoMozillaDirectoryException
   1.120 +     *             if the directory did not exist and could not be created.
   1.121 +     */
   1.122 +    static File getMozillaDirectory(Context context) throws NoMozillaDirectoryException {
   1.123 +        final File mozillaDir = new File(context.getFilesDir(), MOZILLA_DIR_NAME);
   1.124 +        if (mozillaDir.exists() || mozillaDir.mkdirs()) {
   1.125 +            return mozillaDir;
   1.126 +        }
   1.127 +
   1.128 +        // Although this leaks a path to the system log, the path is
   1.129 +        // predictable (unlike a profile directory), so this is fine.
   1.130 +        throw new NoMozillaDirectoryException("Unable to create mozilla directory at " + mozillaDir.getAbsolutePath());
   1.131 +    }
   1.132 +
   1.133 +    /**
   1.134 +     * Discover the default profile name by examining profiles.ini.
   1.135 +     *
   1.136 +     * Package-scoped because {@link GeckoProfile} needs access to it.
   1.137 +     *
   1.138 +     * @return null if there is no "Default" entry in profiles.ini, or the profile
   1.139 +     *         name if there is.
   1.140 +     * @throws NoMozillaDirectoryException
   1.141 +     *             if the Mozilla directory did not exist and could not be created.
   1.142 +     */
   1.143 +    static String findDefaultProfileName(final Context context) throws NoMozillaDirectoryException {
   1.144 +      final INIParser parser = GeckoProfileDirectories.getProfilesINI(getMozillaDirectory(context));
   1.145 +
   1.146 +      for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
   1.147 +          final INISection section = e.nextElement();
   1.148 +          if (section.getIntProperty("Default") == 1) {
   1.149 +              return section.getStringProperty("Name");
   1.150 +          }
   1.151 +      }
   1.152 +
   1.153 +      return null;
   1.154 +    }
   1.155 +
   1.156 +    static Map<String, String> getDefaultProfile(final File mozillaDir) {
   1.157 +        return getMatchingProfiles(mozillaDir, sectionIsDefault, true);
   1.158 +    }
   1.159 +
   1.160 +    static Map<String, String> getProfilesNamed(final File mozillaDir, final String name) {
   1.161 +        final INISectionPredicate predicate = new INISectionPredicate() {
   1.162 +            @Override
   1.163 +            public boolean matches(final INISection section) {
   1.164 +                return name.equals(section.getStringProperty("Name"));
   1.165 +            }
   1.166 +        };
   1.167 +        return getMatchingProfiles(mozillaDir, predicate, true);
   1.168 +    }
   1.169 +
   1.170 +    /**
   1.171 +     * Calls {@link GeckoProfileDirectories#getMatchingProfiles(File, INISectionPredicate, boolean)}
   1.172 +     * with a filter to ensure that all profiles are named.
   1.173 +     */
   1.174 +    static Map<String, String> getAllProfiles(final File mozillaDir) {
   1.175 +        return getMatchingProfiles(mozillaDir, sectionHasName, false);
   1.176 +    }
   1.177 +
   1.178 +    /**
   1.179 +     * Return a mapping from the names of all matching profiles (that is,
   1.180 +     * profiles appearing in profiles.ini that match the supplied predicate) to
   1.181 +     * their absolute paths on disk.
   1.182 +     *
   1.183 +     * @param mozillaDir
   1.184 +     *            a directory containing profiles.ini.
   1.185 +     * @param predicate
   1.186 +     *            a predicate to use when evaluating whether to include a
   1.187 +     *            particular INI section.
   1.188 +     * @param stopOnSuccess
   1.189 +     *            if true, this method will return with the first result that
   1.190 +     *            matches the predicate; if false, all matching results are
   1.191 +     *            included.
   1.192 +     * @return a {@link Map} from name to path.
   1.193 +     */
   1.194 +    public static Map<String, String> getMatchingProfiles(final File mozillaDir, INISectionPredicate predicate, boolean stopOnSuccess) {
   1.195 +        final HashMap<String, String> result = new HashMap<String, String>();
   1.196 +        final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir);
   1.197 +
   1.198 +        for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
   1.199 +            final INISection section = e.nextElement();
   1.200 +            if (predicate == null || predicate.matches(section)) {
   1.201 +                final String name = section.getStringProperty("Name");
   1.202 +                final String pathString = section.getStringProperty("Path");
   1.203 +                final boolean isRelative = section.getIntProperty("IsRelative") == 1;
   1.204 +                final File path = isRelative ? new File(mozillaDir, pathString) : new File(pathString);
   1.205 +                result.put(name, path.getAbsolutePath());
   1.206 +
   1.207 +                if (stopOnSuccess) {
   1.208 +                    return result;
   1.209 +                }
   1.210 +            }
   1.211 +        }
   1.212 +        return result;
   1.213 +    }
   1.214 +
   1.215 +    public static File findProfileDir(final File mozillaDir, final String profileName) throws NoSuchProfileException {
   1.216 +        // Open profiles.ini to find the correct path.
   1.217 +        final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir);
   1.218 +
   1.219 +        for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
   1.220 +            final INISection section = e.nextElement();
   1.221 +            final String name = section.getStringProperty("Name");
   1.222 +            if (name != null && name.equals(profileName)) {
   1.223 +                if (section.getIntProperty("IsRelative") == 1) {
   1.224 +                    return new File(mozillaDir, section.getStringProperty("Path"));
   1.225 +                }
   1.226 +                return new File(section.getStringProperty("Path"));
   1.227 +            }
   1.228 +        }
   1.229 +
   1.230 +        throw new NoSuchProfileException("No profile " + profileName);
   1.231 +    }
   1.232 +}

mercurial