package com.danikula.videocache.file;
import com.danikula.videocache.Cache;
import com.danikula.videocache.ProxyCacheException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* {@link Cache} that uses file for storing data.
*
* @author Alexey Danilov (danikula@gmail.com).
*/
public class FileCache implements Cache {
private static final String TEMP_POSTFIX = ".download";
private final DiskUsage diskUsage;
public File file;
private RandomAccessFile dataFile;
public FileCache(File file) throws ProxyCacheException {
this(file, new UnlimitedDiskUsage());
}
public FileCache(File file, DiskUsage diskUsage) throws ProxyCacheException {
try {
if (diskUsage == null) {
throw new NullPointerException();
}
this.diskUsage = diskUsage;
File directory = file.getParentFile();
Files.makeDir(directory);
boolean completed = file.exists();
this.file = completed ? file : new File(file.getParentFile(), file.getName() + TEMP_POSTFIX);
this.dataFile = new RandomAccessFile(this.file, completed ? "r" : "rw");
} catch (IOException e) {
throw new ProxyCacheException("Error using file " + file + " as disc cache", e);
}
}
@Override
public synchronized long available() throws ProxyCacheException {
try {
return (int) dataFile.length();
} catch (IOException e) {
throw new ProxyCacheException("Error reading length of file " + file, e);
}
}
@Override
public synchronized int read(byte[] buffer, long offset, int length) throws ProxyCacheException {
try {
dataFile.seek(offset);
return dataFile.read(buffer, 0, length);
} catch (IOException e) {
String format = "Error reading %d bytes with offset %d from file[%d bytes] to buffer[%d bytes]";
throw new ProxyCacheException(String.format(format, length, offset, available(), buffer.length), e);
}
}
@Override
public synchronized void append(byte[] data, int length) throws ProxyCacheException {
try {
if (isCompleted()) {
throw new ProxyCacheException("Error append cache: cache file " + file + " is completed!");
}
dataFile.seek(available());
dataFile.write(data, 0, length);
} catch (IOException e) {
String format = "Error writing %d bytes to %s from buffer with size %d";
throw new ProxyCacheException(String.format(format, length, dataFile, data.length), e);
}
}
@Override
public synchronized void close() throws ProxyCacheException {
try {
dataFile.close();
diskUsage.touch(file);
} catch (IOException e) {
throw new ProxyCacheException("Error closing file " + file, e);
}
}
@Override
public synchronized void complete() throws ProxyCacheException {
if (isCompleted()) {
return;
}
close();
String fileName = file.getName().substring(0, file.getName().length() - TEMP_POSTFIX.length());
File completedFile = new File(file.getParentFile(), fileName);
boolean renamed = file.renameTo(completedFile);
if (!renamed) {
throw new ProxyCacheException("Error renaming file " + file + " to " + completedFile + " for completion!");
}
file = completedFile;
try {
dataFile = new RandomAccessFile(file, "r");
diskUsage.touch(file);
} catch (IOException e) {
throw new ProxyCacheException("Error opening " + file + " as disc cache", e);
}
}
@Override
public synchronized boolean isCompleted() {
return !isTempFile(file);
}
/**
* Returns file to be used fo caching. It may as original file passed in constructor as some temp file for not completed cache.
*
* @return file for caching.
*/
public File getFile() {
return file;
}
private boolean isTempFile(File file) {
return file.getName().endsWith(TEMP_POSTFIX);
}
}