summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java')
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java259
1 files changed, 259 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
new file mode 100644
index 000000000..01cdd42bb
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
@@ -0,0 +1,259 @@
+/* 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.util;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.FilenameFilter;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+import java.util.Comparator;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.annotation.RobocopTarget;
+
+public class FileUtils {
+ private static final String LOGTAG = "GeckoFileUtils";
+
+ /*
+ * A basic Filter for checking a filename and age.
+ **/
+ static public class NameAndAgeFilter implements FilenameFilter {
+ final private String mName;
+ final private double mMaxAge;
+
+ public NameAndAgeFilter(String name, double age) {
+ mName = name;
+ mMaxAge = age;
+ }
+
+ @Override
+ public boolean accept(File dir, String filename) {
+ if (mName == null || mName.matches(filename)) {
+ File f = new File(dir, filename);
+
+ if (mMaxAge < 0 || System.currentTimeMillis() - f.lastModified() > mMaxAge) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ @RobocopTarget
+ public static void delTree(File dir, FilenameFilter filter, boolean recurse) {
+ String[] files = null;
+
+ if (filter != null) {
+ files = dir.list(filter);
+ } else {
+ files = dir.list();
+ }
+
+ if (files == null) {
+ return;
+ }
+
+ for (String file : files) {
+ File f = new File(dir, file);
+ delete(f, recurse);
+ }
+ }
+
+ public static boolean delete(File file) throws IOException {
+ return delete(file, true);
+ }
+
+ public static boolean delete(File file, boolean recurse) {
+ if (file.isDirectory() && recurse) {
+ // If the quick delete failed and this is a dir, recursively delete the contents of the dir
+ String files[] = file.list();
+ for (String temp : files) {
+ File fileDelete = new File(file, temp);
+ try {
+ delete(fileDelete);
+ } catch (IOException ex) {
+ Log.i(LOGTAG, "Error deleting " + fileDelete.getPath(), ex);
+ }
+ }
+ }
+
+ // Even if this is a dir, it should now be empty and delete should work
+ return file.delete();
+ }
+
+ /**
+ * A generic solution to read a JSONObject from a file. See
+ * {@link #readStringFromFile(File)} for more details.
+ *
+ * @throws IOException if the file is empty, or another IOException occurs
+ * @throws JSONException if the file could not be converted to a JSONObject.
+ */
+ public static JSONObject readJSONObjectFromFile(final File file) throws IOException, JSONException {
+ if (file.length() == 0) {
+ // Redirect this exception so it's clearer than when the JSON parser catches it.
+ throw new IOException("Given file is empty - the JSON parser cannot create an object from an empty file");
+ }
+ return new JSONObject(readStringFromFile(file));
+ }
+
+ /**
+ * A generic solution to read from a file. For more details,
+ * see {@link #readStringFromInputStreamAndCloseStream(InputStream, int)}.
+ *
+ * This method loads the entire file into memory so will have the expected performance impact.
+ * If you're trying to read a large file, you should be handling your own reading to avoid
+ * out-of-memory errors.
+ */
+ public static String readStringFromFile(final File file) throws IOException {
+ // FileInputStream will throw FileNotFoundException if the file does not exist, but
+ // File.length will return 0 if the file does not exist so we catch it sooner.
+ if (!file.exists()) {
+ throw new FileNotFoundException("Given file, " + file + ", does not exist");
+ } else if (file.length() == 0) {
+ return "";
+ }
+ final int len = (int) file.length(); // includes potential EOF character.
+ return readStringFromInputStreamAndCloseStream(new FileInputStream(file), len);
+ }
+
+ /**
+ * A generic solution to read from an input stream in UTF-8. This function will read from the stream until it
+ * is finished and close the stream - this is necessary to close the wrapping resources.
+ *
+ * For a higher-level method, see {@link #readStringFromFile(File)}.
+ *
+ * Since this is generic, it may not be the most performant for your use case.
+ *
+ * @param bufferSize Size of the underlying buffer for read optimizations - must be > 0.
+ */
+ public static String readStringFromInputStreamAndCloseStream(final InputStream inputStream, final int bufferSize)
+ throws IOException {
+ if (bufferSize <= 0) {
+ // Safe close: it's more important to alert the programmer of
+ // their error than to let them catch and continue on their way.
+ IOUtils.safeStreamClose(inputStream);
+ throw new IllegalArgumentException("Expected buffer size larger than 0. Got: " + bufferSize);
+ }
+
+ final StringBuilder stringBuilder = new StringBuilder(bufferSize);
+ final InputStreamReader reader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
+ try {
+ int charsRead;
+ final char[] buffer = new char[bufferSize];
+ while ((charsRead = reader.read(buffer, 0, bufferSize)) != -1) {
+ stringBuilder.append(buffer, 0, charsRead);
+ }
+ } finally {
+ reader.close();
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * A generic solution to write a JSONObject to a file.
+ * See {@link #writeStringToFile(File, String)} for more details.
+ */
+ public static void writeJSONObjectToFile(final File file, final JSONObject obj) throws IOException {
+ writeStringToFile(file, obj.toString());
+ }
+
+ /**
+ * A generic solution to write to a File - the given file will be overwritten. If it does not exist yet, it will
+ * be created. See {@link #writeStringToOutputStreamAndCloseStream(OutputStream, String)} for more details.
+ */
+ public static void writeStringToFile(final File file, final String str) throws IOException {
+ writeStringToOutputStreamAndCloseStream(new FileOutputStream(file, false), str);
+ }
+
+ /**
+ * A generic solution to write to an output stream in UTF-8. The stream will be closed at the
+ * completion of this method - it's necessary in order to close the wrapping resources.
+ *
+ * For a higher-level method, see {@link #writeStringToFile(File, String)}.
+ *
+ * Since this is generic, it may not be the most performant for your use case.
+ */
+ public static void writeStringToOutputStreamAndCloseStream(final OutputStream outputStream, final String str)
+ throws IOException {
+ try {
+ final OutputStreamWriter writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8"));
+ try {
+ writer.write(str);
+ } finally {
+ writer.close();
+ }
+ } finally {
+ // OutputStreamWriter.close can throw before closing the
+ // underlying stream. For safety, we close here too.
+ outputStream.close();
+ }
+ }
+
+ public static class FilenameWhitelistFilter implements FilenameFilter {
+ private final Set<String> mFilenameWhitelist;
+
+ public FilenameWhitelistFilter(final Set<String> filenameWhitelist) {
+ mFilenameWhitelist = filenameWhitelist;
+ }
+
+ @Override
+ public boolean accept(final File dir, final String filename) {
+ return mFilenameWhitelist.contains(filename);
+ }
+ }
+
+ public static class FilenameRegexFilter implements FilenameFilter {
+ private final Pattern mPattern;
+
+ // Each time `Pattern.matcher` is called, a new matcher is created. We can avoid the excessive object creation
+ // by caching the returned matcher and calling `Matcher.reset` on it. Since Matcher's are not thread safe,
+ // this assumes `FilenameFilter.accept` is not run in parallel (which, according to the source, it is not).
+ private Matcher mCachedMatcher;
+
+ public FilenameRegexFilter(final Pattern pattern) {
+ mPattern = pattern;
+ }
+
+ @Override
+ public boolean accept(final File dir, final String filename) {
+ if (mCachedMatcher == null) {
+ mCachedMatcher = mPattern.matcher(filename);
+ } else {
+ mCachedMatcher.reset(filename);
+ }
+ return mCachedMatcher.matches();
+ }
+ }
+
+ public static class FileLastModifiedComparator implements Comparator<File> {
+ @Override
+ public int compare(final File lhs, final File rhs) {
+ // Long.compare is API 19+.
+ final long lhsModified = lhs.lastModified();
+ final long rhsModified = rhs.lastModified();
+ if (lhsModified < rhsModified) {
+ return -1;
+ } else if (lhsModified == rhsModified) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+ }
+}