| Index: chrome/android/java_staging/src/org/chromium/chrome/browser/crash/CrashFileManager.java
|
| diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/crash/CrashFileManager.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/crash/CrashFileManager.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f3f2ff75dd1a957e123c940e83f8ef338c88c821
|
| --- /dev/null
|
| +++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/crash/CrashFileManager.java
|
| @@ -0,0 +1,202 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.chrome.browser.crash;
|
| +
|
| +import android.util.Log;
|
| +
|
| +import org.chromium.base.VisibleForTesting;
|
| +
|
| +import java.io.File;
|
| +import java.io.FilenameFilter;
|
| +import java.util.Arrays;
|
| +import java.util.Comparator;
|
| +import java.util.regex.Matcher;
|
| +import java.util.regex.Pattern;
|
| +
|
| +/**
|
| + * Responsible for the Crash Report directory. It routinely scans the directory
|
| + * for new Minidump files and takes appropriate actions by either uploading new
|
| + * crash dumps or deleting old ones.
|
| + */
|
| +public class CrashFileManager {
|
| + private static final String TAG = "CrashFileManager";
|
| +
|
| + @VisibleForTesting
|
| + static final String CRASH_DUMP_DIR = "Crash Reports";
|
| +
|
| + // This should mirror the C++ CrashUploadList::kReporterLogFilename variable.
|
| + @VisibleForTesting
|
| + static final String CRASH_DUMP_LOGFILE = CRASH_DUMP_DIR + "/uploads.log";
|
| +
|
| + private static final Pattern MINIDUMP_PATTERN =
|
| + Pattern.compile("\\.dmp([0-9]*)(.try[0-9])?\\z");
|
| +
|
| + private static final Pattern UPLOADED_MINIDUMP_PATTERN = Pattern.compile("\\.up([0-9]*)\\z");
|
| +
|
| + private static final String UPLOADED_MINIDUMP_SUFFIX = ".up";
|
| +
|
| + private static final String UPLOAD_ATTEMPT_DELIMITER = ".try";
|
| +
|
| + @VisibleForTesting
|
| + protected static final String TMP_SUFFIX = ".tmp";
|
| +
|
| + private static final Pattern TMP_PATTERN = Pattern.compile("\\.tmp\\z");
|
| +
|
| + private static Comparator<File> sFileComparator = new Comparator<File>() {
|
| + @Override
|
| + public int compare(File lhs, File rhs) {
|
| + if (lhs == rhs) {
|
| + return 0;
|
| + } else if (lhs.lastModified() < rhs.lastModified()) {
|
| + return -1;
|
| + } else {
|
| + return 1;
|
| + }
|
| + }
|
| + };
|
| +
|
| + @VisibleForTesting
|
| + static boolean deleteFile(File fileToDelete) {
|
| + boolean isSuccess = fileToDelete.delete();
|
| + if (!isSuccess) {
|
| + Log.w(TAG, "Unable to delete " + fileToDelete.getAbsolutePath());
|
| + }
|
| + return isSuccess;
|
| + }
|
| +
|
| + public static String tryIncrementAttemptNumber(File mFileToUpload) {
|
| + String newName = incrementAttemptNumber(mFileToUpload.getPath());
|
| + return mFileToUpload.renameTo(new File(newName)) ? newName : null;
|
| + }
|
| +
|
| + /**
|
| + * @return The file name to rename to after an addition attempt to upload
|
| + */
|
| + @VisibleForTesting
|
| + public static String incrementAttemptNumber(String filename) {
|
| + int numTried = readAttemptNumber(filename);
|
| + if (numTried > 0) {
|
| + int newCount = numTried + 1;
|
| + return filename.replaceAll(UPLOAD_ATTEMPT_DELIMITER + numTried,
|
| + UPLOAD_ATTEMPT_DELIMITER + newCount);
|
| + } else {
|
| + return filename + UPLOAD_ATTEMPT_DELIMITER + "1";
|
| + }
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + public static int readAttemptNumber(String filename) {
|
| + int tryIndex = filename.lastIndexOf(UPLOAD_ATTEMPT_DELIMITER);
|
| + if (tryIndex >= 0) {
|
| + tryIndex += UPLOAD_ATTEMPT_DELIMITER.length();
|
| + // To avoid out of bound exceptions
|
| + if (tryIndex < filename.length()) {
|
| + // We don't try more than 3 times.
|
| + String numTriesString = filename.substring(
|
| + tryIndex, tryIndex + 1);
|
| + try {
|
| + return Integer.parseInt(numTriesString);
|
| + } catch (NumberFormatException ignored) {
|
| + return 0;
|
| + }
|
| + }
|
| + }
|
| + return 0;
|
| + }
|
| +
|
| + public static boolean tryMarkAsUploaded(File mFileToUpload) {
|
| + return mFileToUpload.renameTo(
|
| + new File(mFileToUpload.getPath().replaceAll(
|
| + "\\.dmp", UPLOADED_MINIDUMP_SUFFIX)));
|
| + }
|
| +
|
| + private final File mCacheDir;
|
| +
|
| + public CrashFileManager(File cacheDir) {
|
| + if (cacheDir == null) {
|
| + throw new NullPointerException("Specified context cannot be null.");
|
| + } else if (!cacheDir.isDirectory()) {
|
| + throw new IllegalArgumentException(cacheDir.getAbsolutePath()
|
| + + " is not a directory.");
|
| + }
|
| + mCacheDir = cacheDir;
|
| + }
|
| +
|
| + public File[] getAllMinidumpFiles() {
|
| + return getMatchingFiles(MINIDUMP_PATTERN);
|
| + }
|
| +
|
| + public File[] getAllMinidumpFilesSorted() {
|
| + File[] minidumps = getAllMinidumpFiles();
|
| + Arrays.sort(minidumps, sFileComparator);
|
| + return minidumps;
|
| + }
|
| +
|
| + public void cleanOutAllNonFreshMinidumpFiles() {
|
| + for (File f : getAllUploadedFiles()) {
|
| + deleteFile(f);
|
| + }
|
| + for (File f : getAllTempFiles()) {
|
| + deleteFile(f);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Deletes all files including unsent crash reports.
|
| + * Note: This method is called from multiple threads, but it is not thread-safe. It will
|
| + * generate warning messages in logs if race condition occurs.
|
| + */
|
| + @VisibleForTesting
|
| + public void cleanAllMiniDumps() {
|
| + cleanOutAllNonFreshMinidumpFiles();
|
| +
|
| + for (File f : getAllMinidumpFiles()) {
|
| + deleteFile(f);
|
| + }
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + File[] getMatchingFiles(final Pattern pattern) {
|
| + // Get dump dir and get all files with specified suffix.. The path
|
| + // constructed here must match chrome_paths.cc (see case
|
| + // chrome::DIR_CRASH_DUMPS).
|
| + File crashDir = getCrashDirectory();
|
| + if (!crashDir.exists()) {
|
| + Log.w(TAG, crashDir.getAbsolutePath() + " does not exist!");
|
| + return new File[] {};
|
| + }
|
| + if (!crashDir.isDirectory()) {
|
| + Log.w(TAG, crashDir.getAbsolutePath() + " is not a directory!");
|
| + return new File[] {};
|
| + }
|
| + File[] minidumps = crashDir.listFiles(new FilenameFilter() {
|
| + @Override
|
| + public boolean accept(File dir, String filename) {
|
| + Matcher match = pattern.matcher(filename);
|
| + int tries = readAttemptNumber(filename);
|
| + return match.find() && tries < MinidumpUploadService.MAX_TRIES_ALLOWED;
|
| + }
|
| + });
|
| + return minidumps;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + File[] getAllUploadedFiles() {
|
| + return getMatchingFiles(UPLOADED_MINIDUMP_PATTERN);
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + File getCrashDirectory() {
|
| + return new File(mCacheDir, CRASH_DUMP_DIR);
|
| + }
|
| +
|
| + File getCrashUploadLogFile() {
|
| + return new File(mCacheDir, CRASH_DUMP_LOGFILE);
|
| + }
|
| +
|
| + private File[] getAllTempFiles() {
|
| + return getMatchingFiles(TMP_PATTERN);
|
| + }
|
| +}
|
|
|