diff options
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.java | 259 |
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; + } + } + } +} |