FileDocCategorySizeDatePackage
FileRotator.javaAPI DocAndroid 5.1 API15505Thu Mar 12 22:22:10 GMT 2015com.android.internal.util

FileRotator

public class FileRotator extends Object
Utility that rotates files over time, similar to {@code logrotate}. There is a single "active" file, which is periodically rotated into historical files, and eventually deleted entirely. Files are stored under a specific directory with a well-known prefix.

Instead of manipulating files directly, users implement interfaces that perform operations on {@link InputStream} and {@link OutputStream}. This enables atomic rewriting of file contents in {@link #rewriteActive(Rewriter, long)}.

Users must periodically call {@link #maybeRotate(long)} to perform actual rotation. Not inherently thread safe.

Fields Summary
private static final String
TAG
private static final boolean
LOGD
private final File
mBasePath
private final String
mPrefix
private final long
mRotateAgeMillis
private final long
mDeleteAgeMillis
private static final String
SUFFIX_BACKUP
private static final String
SUFFIX_NO_BACKUP
Constructors Summary
public FileRotator(File basePath, String prefix, long rotateAgeMillis, long deleteAgeMillis)
Create a file rotator.

param
basePath Directory under which all files will be placed.
param
prefix Filename prefix used to identify this rotator.
param
rotateAgeMillis Age in milliseconds beyond which an active file may be rotated into a historical file.
param
deleteAgeMillis Age in milliseconds beyond which a rotated file may be deleted.


    // TODO: provide method to append to active file

                            
       
             
    

                   
       
             
    

                           
          
          
          
    

                                                                                    
             
        mBasePath = Preconditions.checkNotNull(basePath);
        mPrefix = Preconditions.checkNotNull(prefix);
        mRotateAgeMillis = rotateAgeMillis;
        mDeleteAgeMillis = deleteAgeMillis;

        // ensure that base path exists
        mBasePath.mkdirs();

        // recover any backup files
        for (String name : mBasePath.list()) {
            if (!name.startsWith(mPrefix)) continue;

            if (name.endsWith(SUFFIX_BACKUP)) {
                if (LOGD) Slog.d(TAG, "recovering " + name);

                final File backupFile = new File(mBasePath, name);
                final File file = new File(
                        mBasePath, name.substring(0, name.length() - SUFFIX_BACKUP.length()));

                // write failed with backup; recover last file
                backupFile.renameTo(file);

            } else if (name.endsWith(SUFFIX_NO_BACKUP)) {
                if (LOGD) Slog.d(TAG, "recovering " + name);

                final File noBackupFile = new File(mBasePath, name);
                final File file = new File(
                        mBasePath, name.substring(0, name.length() - SUFFIX_NO_BACKUP.length()));

                // write failed without backup; delete both
                noBackupFile.delete();
                file.delete();
            }
        }
    
Methods Summary
public voidcombineActive(com.android.internal.util.FileRotator$Reader reader, com.android.internal.util.FileRotator$Writer writer, long currentTimeMillis)

        rewriteActive(new Rewriter() {
            @Override
            public void reset() {
                // ignored
            }

            @Override
            public void read(InputStream in) throws IOException {
                reader.read(in);
            }

            @Override
            public boolean shouldWrite() {
                return true;
            }

            @Override
            public void write(OutputStream out) throws IOException {
                writer.write(out);
            }
        }, currentTimeMillis);
    
public voiddeleteAll()
Delete all files managed by this rotator.

        final FileInfo info = new FileInfo(mPrefix);
        for (String name : mBasePath.list()) {
            if (info.parse(name)) {
                // delete each file that matches parser
                new File(mBasePath, name).delete();
            }
        }
    
public voiddumpAll(java.io.OutputStream os)
Dump all files managed by this rotator for debugging purposes.

        final ZipOutputStream zos = new ZipOutputStream(os);
        try {
            final FileInfo info = new FileInfo(mPrefix);
            for (String name : mBasePath.list()) {
                if (info.parse(name)) {
                    final ZipEntry entry = new ZipEntry(name);
                    zos.putNextEntry(entry);

                    final File file = new File(mBasePath, name);
                    final FileInputStream is = new FileInputStream(file);
                    try {
                        Streams.copy(is, zos);
                    } finally {
                        IoUtils.closeQuietly(is);
                    }

                    zos.closeEntry();
                }
            }
        } finally {
            IoUtils.closeQuietly(zos);
        }
    
private java.lang.StringgetActiveName(long currentTimeMillis)
Return the currently active file, which may not exist yet.

        String oldestActiveName = null;
        long oldestActiveStart = Long.MAX_VALUE;

        final FileInfo info = new FileInfo(mPrefix);
        for (String name : mBasePath.list()) {
            if (!info.parse(name)) continue;

            // pick the oldest active file which covers current time
            if (info.isActive() && info.startMillis < currentTimeMillis
                    && info.startMillis < oldestActiveStart) {
                oldestActiveName = name;
                oldestActiveStart = info.startMillis;
            }
        }

        if (oldestActiveName != null) {
            return oldestActiveName;
        } else {
            // no active file found above; create one starting now
            info.startMillis = currentTimeMillis;
            info.endMillis = Long.MAX_VALUE;
            return info.build();
        }
    
public voidmaybeRotate(long currentTimeMillis)
Examine all files managed by this rotator, renaming or deleting if their age matches the configured thresholds.

        final long rotateBefore = currentTimeMillis - mRotateAgeMillis;
        final long deleteBefore = currentTimeMillis - mDeleteAgeMillis;

        final FileInfo info = new FileInfo(mPrefix);
        String[] baseFiles = mBasePath.list();
        if (baseFiles == null) {
            return;
        }

        for (String name : baseFiles) {
            if (!info.parse(name)) continue;

            if (info.isActive()) {
                if (info.startMillis <= rotateBefore) {
                    // found active file; rotate if old enough
                    if (LOGD) Slog.d(TAG, "rotating " + name);

                    info.endMillis = currentTimeMillis;

                    final File file = new File(mBasePath, name);
                    final File destFile = new File(mBasePath, info.build());
                    file.renameTo(destFile);
                }
            } else if (info.endMillis <= deleteBefore) {
                // found rotated file; delete if old enough
                if (LOGD) Slog.d(TAG, "deleting " + name);

                final File file = new File(mBasePath, name);
                file.delete();
            }
        }
    
private static voidreadFile(java.io.File file, com.android.internal.util.FileRotator$Reader reader)

        final FileInputStream fis = new FileInputStream(file);
        final BufferedInputStream bis = new BufferedInputStream(fis);
        try {
            reader.read(bis);
        } finally {
            IoUtils.closeQuietly(bis);
        }
    
public voidreadMatching(com.android.internal.util.FileRotator$Reader reader, long matchStartMillis, long matchEndMillis)
Read any rotated data that overlap the requested time range.

        final FileInfo info = new FileInfo(mPrefix);
        for (String name : mBasePath.list()) {
            if (!info.parse(name)) continue;

            // read file when it overlaps
            if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) {
                if (LOGD) Slog.d(TAG, "reading matching " + name);

                final File file = new File(mBasePath, name);
                readFile(file, reader);
            }
        }
    
private static java.io.IOExceptionrethrowAsIoException(java.lang.Throwable t)

        if (t instanceof IOException) {
            throw (IOException) t;
        } else {
            throw new IOException(t.getMessage(), t);
        }
    
public voidrewriteActive(com.android.internal.util.FileRotator$Rewriter rewriter, long currentTimeMillis)
Process currently active file, first reading any existing data, then writing modified data. Maintains a backup during write, which is restored if the write fails.

        final String activeName = getActiveName(currentTimeMillis);
        rewriteSingle(rewriter, activeName);
    
public voidrewriteAll(com.android.internal.util.FileRotator$Rewriter rewriter)
Process all files managed by this rotator, usually to rewrite historical data. Each file is processed atomically.

        final FileInfo info = new FileInfo(mPrefix);
        for (String name : mBasePath.list()) {
            if (!info.parse(name)) continue;

            // process each file that matches parser
            rewriteSingle(rewriter, name);
        }
    
private voidrewriteSingle(com.android.internal.util.FileRotator$Rewriter rewriter, java.lang.String name)
Process a single file atomically, first reading any existing data, then writing modified data. Maintains a backup during write, which is restored if the write fails.

        if (LOGD) Slog.d(TAG, "rewriting " + name);

        final File file = new File(mBasePath, name);
        final File backupFile;

        rewriter.reset();

        if (file.exists()) {
            // read existing data
            readFile(file, rewriter);

            // skip when rewriter has nothing to write
            if (!rewriter.shouldWrite()) return;

            // backup existing data during write
            backupFile = new File(mBasePath, name + SUFFIX_BACKUP);
            file.renameTo(backupFile);

            try {
                writeFile(file, rewriter);

                // write success, delete backup
                backupFile.delete();
            } catch (Throwable t) {
                // write failed, delete file and restore backup
                file.delete();
                backupFile.renameTo(file);
                throw rethrowAsIoException(t);
            }

        } else {
            // create empty backup during write
            backupFile = new File(mBasePath, name + SUFFIX_NO_BACKUP);
            backupFile.createNewFile();

            try {
                writeFile(file, rewriter);

                // write success, delete empty backup
                backupFile.delete();
            } catch (Throwable t) {
                // write failed, delete file and empty backup
                file.delete();
                backupFile.delete();
                throw rethrowAsIoException(t);
            }
        }
    
private static voidwriteFile(java.io.File file, com.android.internal.util.FileRotator$Writer writer)

        final FileOutputStream fos = new FileOutputStream(file);
        final BufferedOutputStream bos = new BufferedOutputStream(fos);
        try {
            writer.write(bos);
            bos.flush();
        } finally {
            FileUtils.sync(fos);
            IoUtils.closeQuietly(bos);
        }