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