/*
* Kontalk Android client
* Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kontalk.util;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Flushable;
import java.io.IOException;
/**
* A simplified FileWriter capable of rotating a file after a given
* amount of bytes have been written to the underlying file.
* @author Daniele Ricci
*/
public class RotatingFileWriter implements Flushable, Closeable {
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
/** Rotate at this amount of bytes. */
private static final long ROTATE_AT = 1048576;
/** How many old lines to delete when rotating. */
private static final int DELETE_OLD = 100;
private final long mRotateAt;
private final long mDeleteOld;
private final File mLogFile;
private FileWriter mWriter;
/** Caches the amount of bytes written in the current file. */
private long mSize;
public RotatingFileWriter(File logFile) throws IOException {
this(logFile, ROTATE_AT, DELETE_OLD);
}
public RotatingFileWriter(File logFile, long rotateAt, int deleteOld) throws IOException {
super();
mLogFile = logFile;
mWriter = new FileWriter(logFile, true);
mSize = mLogFile.length();
mRotateAt = rotateAt;
mDeleteOld = deleteOld;
}
@Override
public synchronized void flush() throws IOException {
mWriter.flush();
}
@Override
public synchronized void close() throws IOException {
mWriter.close();
mWriter = null;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public synchronized void abort() throws IOException {
close();
mLogFile.delete();
}
private void write(String str) throws IOException {
mWriter.write(str);
mSize += str.length();
}
private void newLine() throws IOException {
write(LINE_SEPARATOR);
}
private void print(String s) throws IOException {
if (s == null) {
s = "null";
}
write(s);
}
public synchronized void println(String x) throws IOException {
print(x);
newLine();
checkRotate();
}
private void checkRotate() throws IOException {
if (mSize >= mRotateAt) {
mWriter.close();
if (!rotate())
throw new IOException("Unable to rotate log file");
mWriter = new FileWriter(mLogFile, true);
}
}
private boolean rotate() throws IOException {
// rename the current file
File oldFile = new File(mLogFile.toString() + ".old");
File newFile = new File(mLogFile.toString());
if (newFile.renameTo(oldFile)) {
BufferedReader oldLog = null;
FileWriter rotatedLog = null;
try {
// open the old file and start writing lines
oldLog = new BufferedReader(new FileReader(oldFile));
rotatedLog = new FileWriter(mLogFile.toString());
long lineCount = 0;
String line;
while ((line = oldLog.readLine()) != null) {
lineCount++;
// skip up to DELETE_OLD lines, then start writing lines
if (lineCount > mDeleteOld) {
rotatedLog.write(line);
rotatedLog.write(LINE_SEPARATOR);
}
}
}
finally {
SystemUtils.closeStream(oldLog);
SystemUtils.closeStream(rotatedLog);
oldFile.delete();
}
mSize = mLogFile.length();
return true;
}
else {
// we couldn't rename the old log file
// we just delete it to make space for a new one
return mLogFile.delete();
}
}
}