1a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey/*
2a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * Copyright (C) 2012 The Android Open Source Project
3a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey *
4a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * you may not use this file except in compliance with the License.
6a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * You may obtain a copy of the License at
7a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey *
8a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey *
10a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
11a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * See the License for the specific language governing permissions and
14a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * limitations under the License.
15a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey */
16a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
17a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeypackage com.android.internal.util;
18a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
19a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport android.os.FileUtils;
2063abc37356728c0575d6a62a203102ae6d97953bJeff Sharkeyimport android.util.Slog;
21a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
22a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport java.io.BufferedInputStream;
23a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport java.io.BufferedOutputStream;
24a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport java.io.File;
25a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport java.io.FileInputStream;
26a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport java.io.FileOutputStream;
27a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport java.io.IOException;
28a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport java.io.InputStream;
29a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport java.io.OutputStream;
306de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkeyimport java.util.zip.ZipEntry;
316de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkeyimport java.util.zip.ZipOutputStream;
32a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
33a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeyimport libcore.io.IoUtils;
346de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkeyimport libcore.io.Streams;
35a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
36a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey/**
37a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * Utility that rotates files over time, similar to {@code logrotate}. There is
38a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * a single "active" file, which is periodically rotated into historical files,
39a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * and eventually deleted entirely. Files are stored under a specific directory
40a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * with a well-known prefix.
41a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * <p>
42a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * Instead of manipulating files directly, users implement interfaces that
43a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * perform operations on {@link InputStream} and {@link OutputStream}. This
44a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * enables atomic rewriting of file contents in
4563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey * {@link #rewriteActive(Rewriter, long)}.
46a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * <p>
47a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * Users must periodically call {@link #maybeRotate(long)} to perform actual
48a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey * rotation. Not inherently thread safe.
49a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey */
50a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkeypublic class FileRotator {
5163abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    private static final String TAG = "FileRotator";
52e7bb71d26943fbb053139e1e34203df4c2afaa9bJeff Sharkey    private static final boolean LOGD = false;
5363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
54a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private final File mBasePath;
55a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private final String mPrefix;
56a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private final long mRotateAgeMillis;
57a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private final long mDeleteAgeMillis;
58a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
59a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private static final String SUFFIX_BACKUP = ".backup";
60a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private static final String SUFFIX_NO_BACKUP = ".no_backup";
61a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
62a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    // TODO: provide method to append to active file
63a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
64a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    /**
65a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * External class that reads data from a given {@link InputStream}. May be
66a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * called multiple times when reading rotated data.
67a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     */
68a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    public interface Reader {
69a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        public void read(InputStream in) throws IOException;
70a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
71a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
72a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    /**
73a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * External class that writes data to a given {@link OutputStream}.
74a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     */
75a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    public interface Writer {
76a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        public void write(OutputStream out) throws IOException;
77a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
78a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
79a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    /**
8063abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * External class that reads existing data from given {@link InputStream},
8163abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * then writes any modified data to {@link OutputStream}.
8263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     */
8363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    public interface Rewriter extends Reader, Writer {
8463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        public void reset();
8563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        public boolean shouldWrite();
8663abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    }
8763abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
8863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    /**
89a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * Create a file rotator.
90a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     *
91a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * @param basePath Directory under which all files will be placed.
92a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * @param prefix Filename prefix used to identify this rotator.
93a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * @param rotateAgeMillis Age in milliseconds beyond which an active file
94a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     *            may be rotated into a historical file.
95a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * @param deleteAgeMillis Age in milliseconds beyond which a rotated file
96a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     *            may be deleted.
97a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     */
98a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    public FileRotator(File basePath, String prefix, long rotateAgeMillis, long deleteAgeMillis) {
99a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        mBasePath = Preconditions.checkNotNull(basePath);
100a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        mPrefix = Preconditions.checkNotNull(prefix);
101a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        mRotateAgeMillis = rotateAgeMillis;
102a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        mDeleteAgeMillis = deleteAgeMillis;
103a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
104a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        // ensure that base path exists
105a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        mBasePath.mkdirs();
106a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
107a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        // recover any backup files
108a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        for (String name : mBasePath.list()) {
109a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (!name.startsWith(mPrefix)) continue;
110a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
111a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (name.endsWith(SUFFIX_BACKUP)) {
11263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                if (LOGD) Slog.d(TAG, "recovering " + name);
11363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
114a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                final File backupFile = new File(mBasePath, name);
115a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                final File file = new File(
116a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                        mBasePath, name.substring(0, name.length() - SUFFIX_BACKUP.length()));
117a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
118a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                // write failed with backup; recover last file
119a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                backupFile.renameTo(file);
120a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
121a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            } else if (name.endsWith(SUFFIX_NO_BACKUP)) {
12263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                if (LOGD) Slog.d(TAG, "recovering " + name);
12363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
124a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                final File noBackupFile = new File(mBasePath, name);
125a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                final File file = new File(
126a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                        mBasePath, name.substring(0, name.length() - SUFFIX_NO_BACKUP.length()));
127a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
128a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                // write failed without backup; delete both
129a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                noBackupFile.delete();
130a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                file.delete();
131a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            }
132a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
133a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
134a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
135a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    /**
13663abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * Delete all files managed by this rotator.
13763abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     */
13863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    public void deleteAll() {
13963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        final FileInfo info = new FileInfo(mPrefix);
14063abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        for (String name : mBasePath.list()) {
1416de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            if (info.parse(name)) {
1426de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                // delete each file that matches parser
1436de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                new File(mBasePath, name).delete();
1446de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            }
1456de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey        }
1466de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey    }
1476de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey
1486de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey    /**
1496de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey     * Dump all files managed by this rotator for debugging purposes.
1506de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey     */
1516de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey    public void dumpAll(OutputStream os) throws IOException {
1526de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey        final ZipOutputStream zos = new ZipOutputStream(os);
1536de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey        try {
1546de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            final FileInfo info = new FileInfo(mPrefix);
1556de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            for (String name : mBasePath.list()) {
1566de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                if (info.parse(name)) {
1576de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                    final ZipEntry entry = new ZipEntry(name);
1586de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                    zos.putNextEntry(entry);
15963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
1606de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                    final File file = new File(mBasePath, name);
1616de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                    final FileInputStream is = new FileInputStream(file);
1626de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                    try {
1636de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                        Streams.copy(is, zos);
1646de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                    } finally {
1656de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                        IoUtils.closeQuietly(is);
1666de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                    }
1676de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey
1686de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                    zos.closeEntry();
1696de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                }
1706de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            }
1716de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey        } finally {
1726de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            IoUtils.closeQuietly(zos);
17363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        }
17463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    }
17563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
17663abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    /**
17763abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * Process currently active file, first reading any existing data, then
17863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * writing modified data. Maintains a backup during write, which is restored
17963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * if the write fails.
180a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     */
18163abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    public void rewriteActive(Rewriter rewriter, long currentTimeMillis)
182a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            throws IOException {
183a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final String activeName = getActiveName(currentTimeMillis);
18463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        rewriteSingle(rewriter, activeName);
18563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    }
18663abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
18763abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    @Deprecated
18863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    public void combineActive(final Reader reader, final Writer writer, long currentTimeMillis)
18963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            throws IOException {
19063abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        rewriteActive(new Rewriter() {
1916de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            @Override
19263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            public void reset() {
19363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                // ignored
19463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            }
19563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
1966de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            @Override
19763abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            public void read(InputStream in) throws IOException {
19863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                reader.read(in);
19963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            }
200a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
2016de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            @Override
20263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            public boolean shouldWrite() {
20363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                return true;
20463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            }
20563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
2066de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            @Override
20763abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            public void write(OutputStream out) throws IOException {
20863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                writer.write(out);
20963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            }
21063abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        }, currentTimeMillis);
21163abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    }
21263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
21363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    /**
21463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * Process all files managed by this rotator, usually to rewrite historical
21563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * data. Each file is processed atomically.
21663abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     */
21763abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    public void rewriteAll(Rewriter rewriter) throws IOException {
21863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        final FileInfo info = new FileInfo(mPrefix);
21963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        for (String name : mBasePath.list()) {
22063abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            if (!info.parse(name)) continue;
22163abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
22263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            // process each file that matches parser
22363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            rewriteSingle(rewriter, name);
22463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        }
22563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    }
22663abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
22763abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    /**
22863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * Process a single file atomically, first reading any existing data, then
22963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * writing modified data. Maintains a backup during write, which is restored
23063abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     * if the write fails.
23163abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey     */
23263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey    private void rewriteSingle(Rewriter rewriter, String name) throws IOException {
23363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        if (LOGD) Slog.d(TAG, "rewriting " + name);
23463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
23563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        final File file = new File(mBasePath, name);
236a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final File backupFile;
237a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
23863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey        rewriter.reset();
23963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
240a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        if (file.exists()) {
241a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            // read existing data
24263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            readFile(file, rewriter);
24363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
24463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            // skip when rewriter has nothing to write
24563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            if (!rewriter.shouldWrite()) return;
246a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
247a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            // backup existing data during write
24863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            backupFile = new File(mBasePath, name + SUFFIX_BACKUP);
249a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            file.renameTo(backupFile);
250a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
251a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            try {
25263abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                writeFile(file, rewriter);
253a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
254a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                // write success, delete backup
255a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                backupFile.delete();
2566de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            } catch (Throwable t) {
257a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                // write failed, delete file and restore backup
258a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                file.delete();
259a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                backupFile.renameTo(file);
2606de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                throw rethrowAsIoException(t);
261a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            }
262a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
263a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        } else {
264a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            // create empty backup during write
26563abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            backupFile = new File(mBasePath, name + SUFFIX_NO_BACKUP);
266a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            backupFile.createNewFile();
267a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
268a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            try {
26963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                writeFile(file, rewriter);
270a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
271a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                // write success, delete empty backup
272a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                backupFile.delete();
2736de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            } catch (Throwable t) {
274a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                // write failed, delete file and empty backup
275a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                file.delete();
276a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                backupFile.delete();
2776de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey                throw rethrowAsIoException(t);
278a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            }
279a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
280a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
281a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
282a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    /**
283a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * Read any rotated data that overlap the requested time range.
284a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     */
285a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    public void readMatching(Reader reader, long matchStartMillis, long matchEndMillis)
286a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            throws IOException {
287a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final FileInfo info = new FileInfo(mPrefix);
288a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        for (String name : mBasePath.list()) {
289a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (!info.parse(name)) continue;
290a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
291a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            // read file when it overlaps
292a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) {
29363abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                if (LOGD) Slog.d(TAG, "reading matching " + name);
29463abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
295a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                final File file = new File(mBasePath, name);
296a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                readFile(file, reader);
297a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            }
298a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
299a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
300a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
301a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    /**
302a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * Return the currently active file, which may not exist yet.
303a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     */
304a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private String getActiveName(long currentTimeMillis) {
305a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        String oldestActiveName = null;
306a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        long oldestActiveStart = Long.MAX_VALUE;
307a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
308a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final FileInfo info = new FileInfo(mPrefix);
309a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        for (String name : mBasePath.list()) {
310a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (!info.parse(name)) continue;
311a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
312a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            // pick the oldest active file which covers current time
313a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (info.isActive() && info.startMillis < currentTimeMillis
314a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                    && info.startMillis < oldestActiveStart) {
315a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                oldestActiveName = name;
316a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                oldestActiveStart = info.startMillis;
317a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            }
318a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
319a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
320a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        if (oldestActiveName != null) {
321a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            return oldestActiveName;
322a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        } else {
323a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            // no active file found above; create one starting now
324a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            info.startMillis = currentTimeMillis;
325a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            info.endMillis = Long.MAX_VALUE;
326a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            return info.build();
327a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
328a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
329a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
330a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    /**
331a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * Examine all files managed by this rotator, renaming or deleting if their
332a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * age matches the configured thresholds.
333a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     */
334a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    public void maybeRotate(long currentTimeMillis) {
335a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final long rotateBefore = currentTimeMillis - mRotateAgeMillis;
336a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final long deleteBefore = currentTimeMillis - mDeleteAgeMillis;
337a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
338a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final FileInfo info = new FileInfo(mPrefix);
339bbf1861fdd2ba250354c060fe70f0ee7cbe93877Mikael Gullstrand        String[] baseFiles = mBasePath.list();
340bbf1861fdd2ba250354c060fe70f0ee7cbe93877Mikael Gullstrand        if (baseFiles == null) {
341bbf1861fdd2ba250354c060fe70f0ee7cbe93877Mikael Gullstrand            return;
342bbf1861fdd2ba250354c060fe70f0ee7cbe93877Mikael Gullstrand        }
343bbf1861fdd2ba250354c060fe70f0ee7cbe93877Mikael Gullstrand
344bbf1861fdd2ba250354c060fe70f0ee7cbe93877Mikael Gullstrand        for (String name : baseFiles) {
345a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (!info.parse(name)) continue;
346a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
347a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (info.isActive()) {
34863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                if (info.startMillis <= rotateBefore) {
34963abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                    // found active file; rotate if old enough
35063abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                    if (LOGD) Slog.d(TAG, "rotating " + name);
35163abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
352a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                    info.endMillis = currentTimeMillis;
353a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
354a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                    final File file = new File(mBasePath, name);
355a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                    final File destFile = new File(mBasePath, info.build());
356a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                    file.renameTo(destFile);
357a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                }
35863abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey            } else if (info.endMillis <= deleteBefore) {
359a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                // found rotated file; delete if old enough
36063abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey                if (LOGD) Slog.d(TAG, "deleting " + name);
36163abc37356728c0575d6a62a203102ae6d97953bJeff Sharkey
362a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                final File file = new File(mBasePath, name);
363a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                file.delete();
364a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            }
365a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
366a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
367a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
368a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private static void readFile(File file, Reader reader) throws IOException {
369a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final FileInputStream fis = new FileInputStream(file);
370a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final BufferedInputStream bis = new BufferedInputStream(fis);
371a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        try {
372a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            reader.read(bis);
373a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        } finally {
374a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            IoUtils.closeQuietly(bis);
375a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
376a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
377a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
378a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private static void writeFile(File file, Writer writer) throws IOException {
379a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final FileOutputStream fos = new FileOutputStream(file);
380a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        final BufferedOutputStream bos = new BufferedOutputStream(fos);
381a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        try {
382a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            writer.write(bos);
383a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            bos.flush();
384a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        } finally {
385a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            FileUtils.sync(fos);
386a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            IoUtils.closeQuietly(bos);
387a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
388a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
389a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
3906de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey    private static IOException rethrowAsIoException(Throwable t) throws IOException {
3916de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey        if (t instanceof IOException) {
3926de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            throw (IOException) t;
3936de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey        } else {
3946de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey            throw new IOException(t.getMessage(), t);
3956de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey        }
3966de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey    }
3976de357e4d10fa5977ab9a6c665dc858765e95d34Jeff Sharkey
398a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    /**
399a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * Details for a rotated file, either parsed from an existing filename, or
400a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     * ready to be built into a new filename.
401a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey     */
402a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    private static class FileInfo {
403a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        public final String prefix;
404a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
405a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        public long startMillis;
406a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        public long endMillis;
407a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
408a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        public FileInfo(String prefix) {
409a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            this.prefix = Preconditions.checkNotNull(prefix);
410a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
411a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
412a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        /**
413a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey         * Attempt parsing the given filename.
414a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey         *
415a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey         * @return Whether parsing was successful.
416a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey         */
417a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        public boolean parse(String name) {
418a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            startMillis = endMillis = -1;
419a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
420a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            final int dotIndex = name.lastIndexOf('.');
421a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            final int dashIndex = name.lastIndexOf('-');
422a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
423a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            // skip when missing time section
424a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (dotIndex == -1 || dashIndex == -1) return false;
425a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
426a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            // skip when prefix doesn't match
427a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (!prefix.equals(name.substring(0, dotIndex))) return false;
428a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
429a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            try {
430a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                startMillis = Long.parseLong(name.substring(dotIndex + 1, dashIndex));
431a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
432a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                if (name.length() - dashIndex == 1) {
433a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                    endMillis = Long.MAX_VALUE;
434a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                } else {
435a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                    endMillis = Long.parseLong(name.substring(dashIndex + 1));
436a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                }
437a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
438a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                return true;
439a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            } catch (NumberFormatException e) {
440a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                return false;
441a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            }
442a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
443a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
444a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        /**
445a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey         * Build current state into filename.
446a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey         */
447a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        public String build() {
448a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            final StringBuilder name = new StringBuilder();
449a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            name.append(prefix).append('.').append(startMillis).append('-');
450a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            if (endMillis != Long.MAX_VALUE) {
451a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey                name.append(endMillis);
452a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            }
453a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            return name.toString();
454a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
455a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey
456a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        /**
457a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey         * Test if current file is active (no end timestamp).
458a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey         */
459a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        public boolean isActive() {
460a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey            return endMillis == Long.MAX_VALUE;
461a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey        }
462a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey    }
463a27a3e8ad7d20dea63ef2d5cb8b6ec7e56c20a89Jeff Sharkey}
464