1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.content.Context; 8 import android.net.Uri; 9 10 import java.io.BufferedOutputStream; 11 import java.io.File; 12 import java.io.FileOutputStream; 13 import java.io.IOException; 14 import java.io.InputStream; 15 import java.io.OutputStream; 16 import java.util.List; 17 import java.util.Locale; 18 19 /** 20 * Helper methods for dealing with Files. 21 */ 22 public class FileUtils { 23 private static final String TAG = "FileUtils"; 24 25 /** 26 * Delete the given File and (if it's a directory) everything within it. 27 */ recursivelyDeleteFile(File currentFile)28 public static void recursivelyDeleteFile(File currentFile) { 29 ThreadUtils.assertOnBackgroundThread(); 30 if (currentFile.isDirectory()) { 31 File[] files = currentFile.listFiles(); 32 if (files != null) { 33 for (File file : files) { 34 recursivelyDeleteFile(file); 35 } 36 } 37 } 38 39 if (!currentFile.delete()) Log.e(TAG, "Failed to delete: " + currentFile); 40 } 41 42 /** 43 * Delete the given files or directories by calling {@link #recursivelyDeleteFile(File)}. 44 * @param files The files to delete. 45 */ batchDeleteFiles(List<File> files)46 public static void batchDeleteFiles(List<File> files) { 47 ThreadUtils.assertOnBackgroundThread(); 48 49 for (File file : files) { 50 if (file.exists()) recursivelyDeleteFile(file); 51 } 52 } 53 54 /** 55 * Extracts an asset from the app's APK to a file. 56 * @param context 57 * @param assetName Name of the asset to extract. 58 * @param dest File to extract the asset to. 59 * @return true on success. 60 */ extractAsset(Context context, String assetName, File dest)61 public static boolean extractAsset(Context context, String assetName, File dest) { 62 InputStream inputStream = null; 63 OutputStream outputStream = null; 64 try { 65 inputStream = context.getAssets().open(assetName); 66 outputStream = new BufferedOutputStream(new FileOutputStream(dest)); 67 byte[] buffer = new byte[8192]; 68 int c; 69 while ((c = inputStream.read(buffer)) != -1) { 70 outputStream.write(buffer, 0, c); 71 } 72 inputStream.close(); 73 outputStream.close(); 74 return true; 75 } catch (IOException e) { 76 if (inputStream != null) { 77 try { 78 inputStream.close(); 79 } catch (IOException ex) { 80 } 81 } 82 if (outputStream != null) { 83 try { 84 outputStream.close(); 85 } catch (IOException ex) { 86 } 87 } 88 } 89 return false; 90 } 91 92 /** 93 * Atomically copies the data from an input stream into an output file. 94 * @param is Input file stream to read data from. 95 * @param outFile Output file path. 96 * @param buffer Caller-provided buffer. Provided to avoid allocating the same 97 * buffer on each call when copying several files in sequence. 98 * @throws IOException in case of I/O error. 99 */ copyFileStreamAtomicWithBuffer(InputStream is, File outFile, byte[] buffer)100 public static void copyFileStreamAtomicWithBuffer(InputStream is, File outFile, byte[] buffer) 101 throws IOException { 102 File tmpOutputFile = new File(outFile.getPath() + ".tmp"); 103 try (OutputStream os = new FileOutputStream(tmpOutputFile)) { 104 Log.i(TAG, "Writing to %s", outFile); 105 106 int count = 0; 107 while ((count = is.read(buffer, 0, buffer.length)) != -1) { 108 os.write(buffer, 0, count); 109 } 110 } 111 if (!tmpOutputFile.renameTo(outFile)) { 112 throw new IOException(); 113 } 114 } 115 116 /** 117 * Returns a URI that points at the file. 118 * @param file File to get a URI for. 119 * @return URI that points at that file, either as a content:// URI or a file:// URI. 120 */ getUriForFile(File file)121 public static Uri getUriForFile(File file) { 122 // TODO(crbug/709584): Uncomment this when http://crbug.com/709584 has been fixed. 123 // assert !ThreadUtils.runningOnUiThread(); 124 Uri uri = null; 125 126 try { 127 // Try to obtain a content:// URI, which is preferred to a file:// URI so that 128 // receiving apps don't attempt to determine the file's mime type (which often fails). 129 uri = ContentUriUtils.getContentUriFromFile(file); 130 } catch (IllegalArgumentException e) { 131 Log.e(TAG, "Could not create content uri: " + e); 132 } 133 134 if (uri == null) uri = Uri.fromFile(file); 135 136 return uri; 137 } 138 139 /** 140 * Returns the file extension, or an empty string if none. 141 * @param file Name of the file, with or without the full path. 142 * @return empty string if no extension, extension otherwise. 143 */ getExtension(String file)144 public static String getExtension(String file) { 145 int index = file.lastIndexOf('.'); 146 if (index == -1) return ""; 147 return file.substring(index + 1).toLowerCase(Locale.US); 148 } 149 } 150