package net.didion.loopy.impl;
import net.didion.loopy.AccessStream;
import net.didion.loopy.FileSystem;
import net.didion.loopy.LoopyException;
import java.io.RandomAccessFile;
import java.io.File;
import java.io.IOException;
import java.io.FileNotFoundException;
public abstract class AbstractFileSystem implements FileSystem {
/**
* The file containing the file system image.
*/
private File file;
/**
* Whether the image is writeable. Currently, only read-only images
* are supported.
*/
private boolean readOnly;
/**
* Channel to the open file.
*/
private AccessStream channel;
protected long originalSize;
private boolean buggyAndroid;
private byte[] oneMegBuff; // can be static but need sync for creation
private long currentPos;
protected AbstractFileSystem(AccessStream as) throws LoopyException {
channel = as;
readOnly = true;
try {
originalSize = channel.length();
} catch (IOException ex) {
throw new LoopyException("Error opening the file:" + channel, ex);
}
}
protected AbstractFileSystem(File file, boolean readOnly) throws LoopyException {
if (!readOnly) {
throw new IllegalArgumentException(
"Currrently, only read-only is supported");
}
this.file = file;
this.readOnly = readOnly;
try {
// check that the underlying file is valid
checkFile();
// open the channel
this.channel = new AcceeStreamImpl(this.file, getMode(readOnly));
} catch (IOException ex) {
throw new LoopyException("Error opening the file:"+file, ex);
}
buggyAndroid = checkForBuggyAndroid();
}
private boolean checkForBuggyAndroid() {
try {
return Class.forName("android.os.Build$VERSION").getField("SDK_INT").getInt(null) < 11;
//Class.forName("android.os.Build$VERSION_CODES").getField("HONEYCOMB").getInt(null);
} catch (Exception e) {
}
return false;
}
private void checkFile() throws FileNotFoundException {
if (this.readOnly && !file.exists()) {
throw new FileNotFoundException("File does not exist: " + this.file);
}
originalSize = file.exists()?file.length():0;
}
private String getMode(boolean readOnly) {
return (readOnly) ? "r" : "rw";
}
// TODO: close open streams automatically
public synchronized void close() throws LoopyException {
if (isClosed()) {
return;
}
try {
this.channel.close();
} catch (IOException ex) {
throw new LoopyException("Error closing file system", ex);
} finally {
this.channel = null;
}
}
public boolean isClosed() {
return null == this.channel;
}
protected void ensureOpen() throws IllegalStateException {
if (isClosed()) {
throw new IllegalStateException("File has been closed");
}
}
protected void seek(long pos) throws IOException {
ensureOpen();
if (buggyAndroid && pos > Integer.MAX_VALUE) {
if (currentPos == pos)
return;
this.channel.seek(Integer.MAX_VALUE);
int n = (int) (pos - Integer.MAX_VALUE);
synchronized (this) {
if (oneMegBuff == null)
oneMegBuff = new byte[1024 * 1024];
}
for (int i = 0, k = n / 1024 / 1024; i < k; i++)
this.channel.read(oneMegBuff);
for (int i = 0, k = n % (1024 * 1024); i < k; i++)
this.channel.read();
} else
this.channel.seek(pos);
currentPos = pos;
}
protected int read(byte[] buffer, int bufferOffset, int len) throws IOException {
ensureOpen();
if (buggyAndroid) {
int result = this.channel.read(buffer, bufferOffset, len);
currentPos +=result;
return result;
}
return this.channel.read(buffer, bufferOffset, len);
}
static class AcceeStreamImpl extends RandomAccessFile implements AccessStream {
public AcceeStreamImpl(File file, String mode) throws FileNotFoundException {
super(file, mode);
}
}
}