/*
* Lilith - a log event viewer.
* Copyright (C) 2007-2015 Joern Huxhorn
*
* 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 de.huxhorn.lilith.swing.callables;
import de.huxhorn.sulky.codec.filebuffer.DefaultDataStrategy;
import de.huxhorn.sulky.codec.filebuffer.DefaultFileHeaderStrategy;
import de.huxhorn.sulky.codec.filebuffer.DefaultIndexStrategy;
import de.huxhorn.sulky.codec.filebuffer.FileHeader;
import de.huxhorn.sulky.codec.filebuffer.FileHeaderStrategy;
import de.huxhorn.sulky.codec.filebuffer.IndexStrategy;
import de.huxhorn.sulky.codec.filebuffer.SparseDataStrategy;
import de.huxhorn.sulky.io.IOUtilities;
import de.huxhorn.sulky.tasks.AbstractProgressingCallable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Should only be executed on inactive files.
*/
public class IndexingCallable
extends AbstractProgressingCallable<Long>
{
private final Logger logger = LoggerFactory.getLogger(IndexingCallable.class);
private File dataFile;
private File indexFile;
private boolean reindexing;
public IndexingCallable(File dataFile, File indexFile)
{
this(dataFile, indexFile, false);
}
public IndexingCallable(File dataFile, File indexFile, boolean reindexing)
{
this.dataFile = dataFile;
this.indexFile = indexFile;
this.reindexing = reindexing;
}
public boolean isReindexing()
{
return reindexing;
}
public void setReindexing(boolean reindexing)
{
this.reindexing = reindexing;
}
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
public Long call()
throws Exception
{
if(!dataFile.exists())
{
throw new FileNotFoundException("File '" + dataFile.getAbsolutePath() + "' does not exist!");
}
if(!dataFile.isFile())
{
throw new FileNotFoundException("File '" + dataFile.getAbsolutePath() + "' is not a file!");
}
long fileSize = dataFile.length();
setNumberOfSteps(fileSize);
FileHeaderStrategy fhs = new DefaultFileHeaderStrategy();
FileHeader fileHeader = fhs.readFileHeader(dataFile);
if(fileHeader != null)
{
boolean sparse = fileHeader.getMetaData().isSparse();
long offset = fileHeader.getDataOffset();
RandomAccessFile dataRAFile = null;
RandomAccessFile indexRAFile = null;
Exception ex = null;
long counter = 0;
IndexStrategy indexStrategy = new DefaultIndexStrategy();
try
{
dataRAFile = new RandomAccessFile(dataFile, "r");
indexRAFile = new RandomAccessFile(indexFile, "rw");
boolean deleteIndex = !reindexing;
if(reindexing && indexFile.isFile())
{
counter = indexStrategy.getSize(indexRAFile) - 1;
if(counter >= 0)
{
offset = indexStrategy.getOffset(indexRAFile, counter);
if(offset > fileSize)
{
// this means that the index was for a different data file...
// It's just a heuristic, though.
deleteIndex=true;
}
}
else
{
counter=0;
deleteIndex = true;
}
}
else
{
deleteIndex=true; // not strictly necessary but doesn't hurt, either.
}
if(deleteIndex)
{
indexRAFile.setLength(0);
counter = 0;
offset = fileHeader.getDataOffset();
}
while(offset < fileSize)
{
dataRAFile.seek(offset);
int dataSize = dataRAFile.readInt();
if(!sparse)
{
indexStrategy.setOffset(indexRAFile, counter, offset);
offset = offset + dataSize + DefaultDataStrategy.DATA_LENGTH_SIZE;
}
else
{
long index = dataRAFile.readLong();
indexStrategy.setOffset(indexRAFile, index, offset);
offset = offset + dataSize + SparseDataStrategy.DATA_LENGTH_SIZE + SparseDataStrategy.INDEX_SIZE;
}
counter++;
setCurrentStep(offset);
}
}
catch(IOException | InterruptedException e)
{
ex = e;
}
finally
{
closeQuietly(dataRAFile);
closeQuietly(indexRAFile);
}
if(ex != null)
{
if(!indexFile.delete())
{
if(logger.isWarnEnabled())
{
logger.warn("Failed to delete index file '{}'!", indexFile.getAbsolutePath());
}
}
IOUtilities.interruptIfNecessary(ex);
throw ex; // rethrow
}
if(logger.isInfoEnabled()) logger.info("File '{}' has {} entries.", dataFile.getAbsolutePath(), counter);
return counter;
}
else
{
throw new IllegalArgumentException("File '" + dataFile.getAbsolutePath() + "' is not a valid file!");
}
}
public File getDataFile()
{
return dataFile;
}
public File getIndexFile()
{
return indexFile;
}
private static void closeQuietly(RandomAccessFile file)
{
if(file != null)
{
try
{
file.close();
}
catch(IOException e)
{
// ignore
}
}
}
}