package org.opendedup.sdfs.io;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.opendedup.sdfs.Main;
import org.opendedup.util.HashFunctions;
/**
*
* @author annesam WritableCacheBuffer is used to store written data for later
* writing and reading by the DedupFile. WritableCacheBuffers are
* evicted from the file system based LRU. When a writable cache buffer
* is evicted it is then written to the dedup chunk service
*/
public class WritableCacheBuffer extends DedupChunk {
private static final long serialVersionUID = 8325202759315844948L;
private transient static byte[] defaultHash = null;
private ByteBuffer buf = null;
private boolean dirty = false;
private long endPosition = 0;
private int currentLen = 0;
private transient static Logger log = Logger.getLogger("sdfs");
private int bytesWritten = 0;
private DedupFile df;
private ReentrantLock lock = new ReentrantLock();
private boolean closed;
File blockFile = null;
RandomAccessFile raf = null;
boolean rafInit = false;
boolean prevDoop = false;
static {
try {
defaultHash = HashFunctions
.getSHAHashBytes(new byte[Main.CHUNK_LENGTH]);
} catch (Exception e) {
log.log(Level.SEVERE, "error initializing WritableCacheBuffer", e);
}
}
protected WritableCacheBuffer(byte[] hash, long startPos, int length,
DedupFile df) throws IOException {
super(hash, startPos, length, true);
this.df = df;
buf = ByteBuffer.wrap(new byte[Main.CHUNK_LENGTH]);
if (Main.safeSync) {
blockFile = new File(df.getDatabaseDirPath() + File.separator
+ startPos + ".chk");
if (blockFile.exists()) {
log.warning("recovering from unexpected close at " + startPos);
buf = ByteBuffer.wrap(readBlockFile());
}
this.rafInit = true;
}
this.currentLen = 0;
this.setLength(length);
this.endPosition = this.getFilePosition() + this.getLength();
this.setWritable(true);
}
private byte[] readBlockFile() throws IOException {
raf = new RandomAccessFile(blockFile, "r");
byte[] b = new byte[(int) raf.length()];
raf.read(b);
raf.close();
raf = null;
return b;
}
public int getBytesWritten() {
return bytesWritten;
}
public DedupFile getDedupFile() {
return this.df;
}
protected WritableCacheBuffer(DedupChunk dk, DedupFile df)
throws IOException {
super(dk.getHash(), dk.getFilePosition(), dk.getLength(), dk
.isNewChunk());
this.df = df;
buf = ByteBuffer.wrap(new byte[Main.CHUNK_LENGTH]);
if (Main.safeSync) {
blockFile = new File(df.getDatabaseDirPath() + File.separator
+ dk.getFilePosition() + ".chk");
if (blockFile.exists()) {
log.warning("recovering from unexpected close at "
+ dk.getFilePosition());
buf = ByteBuffer.wrap(readBlockFile());
}
if (dk.isNewChunk())
rafInit = true;
}
if (!dk.isNewChunk()) {
try {
byte[] ck = dk.getChunk();
if (ck.length > Main.CHUNK_LENGTH) {
log.info("Alert ! returned chunk to large " + ck.length
+ " > " + Main.CHUNK_LENGTH);
buf = ByteBuffer.wrap(ck);
} else {
buf.put(ck);
}
} catch (Exception e) {
buf.put(new byte[Main.CHUNK_LENGTH]);
log.log(Level.SEVERE,
"unable to get chunk bytes for " + dk.getHash()
+ " at position " + dk.getFilePosition(), e);
}
}
// this.currentLen = 0;
this.currentLen = buf.position();
if (Arrays.equals(dk.getHash(), defaultHash)) {
this.currentLen = 0;
}
this.setLength(buf.capacity());
this.endPosition = this.getFilePosition() + this.getLength();
this.setWritable(true);
}
public boolean sync() throws IOException {
if (Main.safeSync) {
try {
this.lock.lock();
raf = new RandomAccessFile(blockFile, "rw");
raf.getChannel().force(false);
raf.close();
raf = null;
return true;
} catch (Exception e) {
log.log(Level.WARNING, "unable to sync "
+ this.blockFile.getPath(), e);
throw new IOException(e.toString());
} finally {
this.lock.unlock();
}
}
return false;
}
public int capacity() {
return this.buf.capacity();
}
public int position() {
return this.buf.position();
}
public long getEndPosition() {
return endPosition;
}
public byte[] getChunk() throws IOException {
byte b[] = new byte[Main.CHUNK_LENGTH];
try {
this.lock.lock();
// log.info("Buffer pos :" + buf.position() + " len :" +
// buf.capacity());
buf.position(0);
buf.get(b);
} catch (Exception e) {
throw new IOException(e.toString());
} finally {
this.lock.unlock();
}
return b;
}
public int getCurrentLen() {
return currentLen;
}
/**
* Writes to the given target array
*
* @param b
* the source array
* @param pos
* the position within the target array to write to
* @param len
* the length to write from the target array
* @throws BufferClosedException
*/
public void write(byte[] b, int pos) throws BufferClosedException {
if (this.closed)
throw new BufferClosedException("Buffer Closed");
try {
this.lock.lock();
buf.position(pos);
buf.put(b);
if (buf.position() > currentLen)
this.currentLen = buf.position();
if (Main.safeSync) {
raf = new RandomAccessFile(blockFile, "rw");
if (!this.rafInit) {
raf.seek(0);
raf.write(buf.array());
this.rafInit = true;
}
raf.seek(pos);
raf.write(b);
raf.close();
raf = null;
}
this.setDirty(true);
this.bytesWritten = this.bytesWritten + b.length;
} catch (Exception e) {
log.severe("Error while writing data [" + b.length + "] position ["
+ pos + "]");
throw new IllegalArgumentException("error");
} finally {
this.lock.unlock();
}
}
public void truncate(int len) {
try {
this.lock.lock();
if (len < this.currentLen) {
if (!Main.safeSync) {
byte[] b = new byte[Main.CHUNK_LENGTH];
ByteBuffer _buf = ByteBuffer.wrap(b);
_buf.put(buf.array(), 0, len);
buf = _buf;
} else {
this.destroy();
}
}
this.setDirty(true);
this.currentLen = len;
} catch (Exception e) {
log.log(Level.SEVERE, "Error while truncating " + len, e);
throw new IllegalArgumentException("error");
} finally {
this.lock.unlock();
}
}
public boolean isDirty() {
return dirty;
}
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
/*
* public void destroy() { lock.lock(); if (buf != null) { buf.clear(); buf
* = null; } lock.unlock(); }
*/
public String toString() {
return this.getHash() + ":" + this.getFilePosition() + ":"
+ this.getLength() + ":" + this.getEndPosition();
}
protected void open() {
try {
this.lock.lock();
this.closed = false;
} catch (Exception e) {
log.severe("Error while opening");
throw new IllegalArgumentException("error");
} finally {
this.lock.unlock();
}
}
protected boolean isClosed() {
this.lock.lock();
try {
return this.closed;
} catch (Exception e) {
return this.closed;
} finally {
this.lock.unlock();
}
}
public void close() {
try {
this.lock.lock();
this.df.writeCache(this, false);
this.closed = true;
} catch (Exception e) {
log.log(Level.SEVERE, "Error while closing", e);
throw new IllegalArgumentException("error while closing "
+ e.toString());
} finally {
this.lock.unlock();
}
}
public void persist() {
try {
this.lock.lock();
this.df.writeCache(this,true);
this.closed = true;
} catch (Exception e) {
log.log(Level.SEVERE, "Error while closing", e);
throw new IllegalArgumentException("error while closing "
+ e.toString());
} finally {
this.lock.unlock();
}
}
public void destroy() {
if (raf != null) {
try {
this.lock.lock();
try {
raf.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
raf = null;
buf = null;
} catch (Exception e) {
} finally {
this.lock.unlock();
}
}
if (this.blockFile != null) {
blockFile.delete();
blockFile = null;
}
}
public boolean isPrevDoop() {
return prevDoop;
}
public void setPrevDoop(boolean prevDoop) {
this.prevDoop = prevDoop;
}
}