summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
blob: 01cdd42bbad79e602f23b0428afcc48bf0fcbe33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
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;
            }
        }
    }
}