Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d9526907 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Move deleteOlderFiles() to framework, tests.

Supports both age and count based constraints.  Added light docs and
tests to verify behavior.

Bug: 8387555
Change-Id: If4d0dac8bc54ce705c2a339bc09a60ea748728b8
parent 67bea2ef
Loading
Loading
Loading
Loading
+44 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.os;

import android.util.Log;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -25,6 +27,8 @@ import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
@@ -34,6 +38,8 @@ import java.util.zip.CheckedInputStream;
 * @hide
 */
public class FileUtils {
    private static final String TAG = "FileUtils";

    public static final int S_IRWXU = 00700;
    public static final int S_IRUSR = 00400;
    public static final int S_IWUSR = 00200;
@@ -161,7 +167,8 @@ public class FileUtils {
            } else if (max < 0) {  // "tail" mode: keep the last N
                int len;
                boolean rolled = false;
                byte[] last = null, data = null;
                byte[] last = null;
                byte[] data = null;
                do {
                    if (last != null) rolled = true;
                    byte[] tmp = last; last = data; data = tmp;
@@ -237,4 +244,40 @@ public class FileUtils {
            }
        }
    }

    /**
     * Delete older files in a directory until only those matching the given
     * constraints remain.
     *
     * @param minCount Always keep at least this many files.
     * @param minAge Always keep files younger than this age.
     */
    public static void deleteOlderFiles(File dir, int minCount, long minAge) {
        if (minCount < 0 || minAge < 0) {
            throw new IllegalArgumentException("Constraints must be positive or 0");
        }

        final File[] files = dir.listFiles();
        if (files == null) return;

        // Sort with newest files first
        Arrays.sort(files, new Comparator<File>() {
            @Override
            public int compare(File lhs, File rhs) {
                return (int) (rhs.lastModified() - lhs.lastModified());
            }
        });

        // Keep at least minCount files
        for (int i = minCount; i < files.length; i++) {
            final File file = files[i];

            // Keep files newer than minAge
            final long age = System.currentTimeMillis() - file.lastModified();
            if (age > minAge) {
                Log.d(TAG, "Deleting old file " + file);
                file.delete();
            }
        }
    }
}
+99 −17
Original line number Diff line number Diff line
@@ -16,61 +16,65 @@

package android.os;

import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;

import android.content.Context;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;

import com.google.android.collect.Sets;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.util.Arrays;
import java.util.HashSet;

import libcore.io.IoUtils;

@MediumTest
public class FileUtilsTest extends AndroidTestCase {
    private static final String TEST_DATA =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    private File mDir;
    private File mTestFile;
    private File mCopyFile;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        File testDir = getContext().getDir("testing", Context.MODE_PRIVATE);
        mTestFile = new File(testDir, "test.file");
        mCopyFile = new File(testDir, "copy.file");
        FileWriter writer = new FileWriter(mTestFile);
        try {
            writer.write(TEST_DATA, 0, TEST_DATA.length());
        } finally {
            writer.close();
        }
        mDir = getContext().getDir("testing", Context.MODE_PRIVATE);
        mTestFile = new File(mDir, "test.file");
        mCopyFile = new File(mDir, "copy.file");
    }

    @Override
    protected void tearDown() throws Exception {
        if (mTestFile.exists()) mTestFile.delete();
        if (mCopyFile.exists()) mCopyFile.delete();
        IoUtils.deleteContents(mDir);
    }

    // TODO: test setPermissions(), getPermissions()

    @MediumTest
    public void testCopyFile() throws Exception {
        stageFile(mTestFile, TEST_DATA);
        assertFalse(mCopyFile.exists());
        FileUtils.copyFile(mTestFile, mCopyFile);
        assertTrue(mCopyFile.exists());
        assertEquals(TEST_DATA, FileUtils.readTextFile(mCopyFile, 0, null));
    }

    @MediumTest
    public void testCopyToFile() throws Exception {
        final String s = "Foo Bar";
        assertFalse(mCopyFile.exists());
        FileUtils.copyToFile(new ByteArrayInputStream(s.getBytes()), mCopyFile);        assertTrue(mCopyFile.exists());
        FileUtils.copyToFile(new ByteArrayInputStream(s.getBytes()), mCopyFile);
        assertTrue(mCopyFile.exists());
        assertEquals(s, FileUtils.readTextFile(mCopyFile, 0, null));
    }

    @MediumTest
    public void testIsFilenameSafe() throws Exception {
        assertTrue(FileUtils.isFilenameSafe(new File("foobar")));
        assertTrue(FileUtils.isFilenameSafe(new File("a_b-c=d.e/0,1+23")));
@@ -78,8 +82,9 @@ public class FileUtilsTest extends AndroidTestCase {
        assertFalse(FileUtils.isFilenameSafe(new File("foo\nbar")));
    }

    @MediumTest
    public void testReadTextFile() throws Exception {
        stageFile(mTestFile, TEST_DATA);

        assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 0, null));

        assertEquals("ABCDE", FileUtils.readTextFile(mTestFile, 5, null));
@@ -97,8 +102,8 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, -100, "<>"));
    }

    @MediumTest
    public void testReadTextFileWithZeroLengthFile() throws Exception {
        stageFile(mTestFile, TEST_DATA);
        new FileOutputStream(mTestFile).close();  // Zero out the file
        assertEquals("", FileUtils.readTextFile(mTestFile, 0, null));
        assertEquals("", FileUtils.readTextFile(mTestFile, 1, "<>"));
@@ -106,4 +111,81 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals("", FileUtils.readTextFile(mTestFile, -1, "<>"));
        assertEquals("", FileUtils.readTextFile(mTestFile, -10, "<>"));
    }

    public void testDeleteOlderEmptyDir() throws Exception {
        FileUtils.deleteOlderFiles(mDir, 10, WEEK_IN_MILLIS);
        assertDirContents();
    }

    public void testDeleteOlderTypical() throws Exception {
        touch("file1", HOUR_IN_MILLIS);
        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        FileUtils.deleteOlderFiles(mDir, 3, DAY_IN_MILLIS);
        assertDirContents("file1", "file2", "file3");
    }

    public void testDeleteOlderInFuture() throws Exception {
        touch("file1", -HOUR_IN_MILLIS);
        touch("file2", HOUR_IN_MILLIS);
        touch("file3", WEEK_IN_MILLIS);
        FileUtils.deleteOlderFiles(mDir, 0, DAY_IN_MILLIS);
        assertDirContents("file1", "file2");

        touch("file1", -HOUR_IN_MILLIS);
        touch("file2", HOUR_IN_MILLIS);
        touch("file3", WEEK_IN_MILLIS);
        FileUtils.deleteOlderFiles(mDir, 0, DAY_IN_MILLIS);
        assertDirContents("file1", "file2");
    }

    public void testDeleteOlderOnlyAge() throws Exception {
        touch("file1", HOUR_IN_MILLIS);
        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        FileUtils.deleteOlderFiles(mDir, 0, DAY_IN_MILLIS);
        assertDirContents("file1");
    }

    public void testDeleteOlderOnlyCount() throws Exception {
        touch("file1", HOUR_IN_MILLIS);
        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        FileUtils.deleteOlderFiles(mDir, 2, 0);
        assertDirContents("file1", "file2");
    }

    private void touch(String name, long age) throws Exception {
        final File file = new File(mDir, name);
        file.createNewFile();
        file.setLastModified(System.currentTimeMillis() - age);
    }

    private void stageFile(File file, String data) throws Exception {
        FileWriter writer = new FileWriter(file);
        try {
            writer.write(data, 0, data.length());
        } finally {
            writer.close();
        }
    }

    private void assertDirContents(String... expected) {
        final HashSet<String> expectedSet = Sets.newHashSet(expected);
        String[] actual = mDir.list();
        if (actual == null) actual = new String[0];

        assertEquals(
                "Expected " + Arrays.toString(expected) + " but actual " + Arrays.toString(actual),
                expected.length, actual.length);
        for (String actualFile : actual) {
            assertTrue("Unexpected actual file " + actualFile, expectedSet.contains(actualFile));
        }
    }
}
+12 −30
Original line number Diff line number Diff line
@@ -29,17 +29,16 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.FileUtils;
import android.os.SystemProperties;
import android.support.v4.content.FileProvider;
import android.util.Log;
import android.text.format.DateUtils;
import android.util.Patterns;

import com.google.android.collect.Lists;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

/**
 * Receiver that handles finished bugreports, usually by attaching them to an
@@ -54,10 +53,15 @@ public class BugreportReceiver extends BroadcastReceiver {
    private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";

    /**
     * Number of bugreports to retain before deleting the oldest; 4 reports and
     * 4 screenshots are roughly 17MB of disk space.
     * Always keep the newest 8 bugreport files; 4 reports and 4 screenshots are
     * roughly 17MB of disk space.
     */
    private static final int NUM_OLD_FILES = 8;
    private static final int MIN_KEEP_COUNT = 8;

    /**
     * Always keep bugreports taken in the last week.
     */
    private static final long MIN_KEEP_AGE = DateUtils.WEEK_IN_MILLIS;

    @Override
    public void onReceive(Context context, Intent intent) {
@@ -94,7 +98,8 @@ public class BugreportReceiver extends BroadcastReceiver {
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                deleteOlderFiles(bugreportFile.getParentFile(), NUM_OLD_FILES);
                FileUtils.deleteOlderFiles(
                        bugreportFile.getParentFile(), MIN_KEEP_COUNT, MIN_KEEP_AGE);
                result.finish();
                return null;
            }
@@ -164,28 +169,6 @@ public class BugreportReceiver extends BroadcastReceiver {
        return foundAccount;
    }

    /**
     * Delete the oldest files in given directory until only the requested
     * number remain.
     */
    private static void deleteOlderFiles(File dir, int retainNum) {
        final File[] files = dir.listFiles();
        if (files == null) return;

        Arrays.sort(files, new ModifiedComparator());
        for (int i = retainNum; i < files.length; i++) {
            Log.d(TAG, "Deleting old file " + files[i]);
            files[i].delete();
        }
    }

    private static class ModifiedComparator implements Comparator<File> {
        @Override
        public int compare(File lhs, File rhs) {
            return (int) (rhs.lastModified() - lhs.lastModified());
        }
    }

    private static File getFileExtra(Intent intent, String key) {
        final String path = intent.getStringExtra(key);
        if (path != null) {
@@ -194,5 +177,4 @@ public class BugreportReceiver extends BroadcastReceiver {
            return null;
        }
    }

}