package org.opendedup.sdfs.io;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.opendedup.sdfs.Main;
/**
*
* @author annesam This is class that is used as an IO interface between the
* user based file system, such as Fuse, and the Dedup engine. The dedup
* engine is comprised of the SDFS client and the chunk store service.
* The DedupFileChannel is loosely based off of the java FileChannel
* class.
*/
public class DedupFileChannel {
// The dedup file associated with this file channel
private DedupFile df;
// The MetaDataDedupFile associated with this file channel.
public MetaDataDedupFile mf;
private static Logger log = Logger.getLogger(DedupFileChannel.class
.getName());
private boolean writtenTo = false;
private long dups;
private long currentPosition = 0;
private String GUID = UUID.randomUUID().toString();
private ReentrantLock closeLock = new ReentrantLock();
private boolean closed = false;
/**
* Instantiates the DedupFileChannel
*
* @param file
* the MetaDataDedupFile that the filechannel will be opened for
* @throws IOException
*/
protected DedupFileChannel(MetaDataDedupFile file) throws IOException {
df = file.getDedupFile();
mf = file;
if (Main.safeSync) {
mf.sync();
df.sync();
}
log.log(Level.FINER, "Initializing Cached File " + mf.getPath());
}
public boolean isClosed() {
this.closeLock.lock();
try {
return this.closed;
} catch (Exception e) {
return this.closed;
} finally {
this.closeLock.unlock();
}
}
/**
*
* @return the GUID associate with this file channel.
*/
public String getGUID() {
return this.GUID;
}
/**
* Truncate or grow the file
*
* @param siz
* the size of the file channel
* @exception IOException
*/
public synchronized void truncateFile(long siz) throws IOException {
log.finest("Truncating File");
if (siz < mf.length()) {
WritableCacheBuffer writeBuffer = df.getWriteBuffer(siz);
int endPos = (int) (siz - writeBuffer.getFilePosition());
DedupChunk nextDk = df.getHash(writeBuffer.getEndPosition() + 1,
false);
while (nextDk != null) {
log.finest("Removing chunk at position "
+ nextDk.getFilePosition());
df.removeHash(nextDk.getFilePosition());
nextDk = df.getHash(nextDk.getFilePosition()
+ nextDk.getLength() + 1, true);
}
writeBuffer.truncate(endPos);
// df.writeCache(writeBuffer,true);
}
mf.setLastAccessed(System.currentTimeMillis());
mf.setLength(siz, true);
}
/**
*
* @return the number of duplicates found while this file channel is open.
*/
public long getDups() {
return dups;
}
/**
*
* @return the current position the file is reading or writing from
*/
public long position() {
return this.currentPosition;
}
/**
*
* @param pos
* sets the current position of the file
*/
public void position(long pos) {
this.currentPosition = pos;
}
/**
*
* @return the current size of the file
*/
public long size() {
return this.mf.length();
}
/**
*
* @return the path of the file
*/
public String getPath() {
return this.mf.getPath();
}
/**
*
* @return the path of the file
*/
public String getName() {
return this.mf.getPath();
}
/**
*
* @return the MetaDataDedupFile associated with this DedupFileChannel
*/
public MetaDataDedupFile getFile() {
return this.mf;
}
/**
* Forces data to be synced to disk
*
* @param metaData
* true will sync data
* @throws IOException
*/
public void force(boolean metaData) throws IOException {
// FixMe Does not persist chunks. This may be an issue.
// this.writeCache();
if (metaData) {
df.sync();
mf.sync();
}
}
/**
*
* @param lastModified
* sets the last time the data was modified for the underlying
* file
* @throws IOException
*/
public void setLastModified(long lastModified) throws IOException {
mf.setLastModified(lastModified);
}
/**
* writes data to the DedupFile
*
* @param bbuf
* the bytes to write
* @param len
* the length of data to write
* @param pos
* the position within the file to write the data to
* @param offset
* the offset within the bbuf to start the write from
* @throws java.io.IOException
*/
public void writeFile(byte[] bbuf, int len, int pos, long offset)
throws java.io.IOException {
// this.addAio();
try {
this.writtenTo = true;
long _cp = offset;
ByteBuffer buf = ByteBuffer.wrap(bbuf, pos, len);
int bytesLeft = len;
int write = 0;
while (bytesLeft > 0) {
// Check to see if we need a new Write buffer
WritableCacheBuffer writeBuffer = df.getWriteBuffer(_cp);
// Find out where to write to in the buffer
int startPos = (int) (_cp - writeBuffer.getFilePosition());
if (startPos < 0)
log.severe("Error " + _cp + " "
+ writeBuffer.getFilePosition());
// Find out how many total bytes there are left to write in
// this
// loop
int endPos = startPos + bytesLeft;
// If the writebuffer can fit what is left, write it and
// quit.
if ((endPos) <= writeBuffer.capacity()) {
byte[] b = new byte[bytesLeft];
buf.get(b);
writeBuffer.write(b, startPos);
write = write + bytesLeft;
_cp = _cp + bytesLeft;
bytesLeft = 0;
} else {
int _len = writeBuffer.capacity() - startPos;
byte[] b = new byte[_len];
buf.get(b);
writeBuffer.write(b, startPos);
_cp = _cp + _len;
bytesLeft = bytesLeft - _len;
write = write + _len;
}
this.currentPosition = _cp;
if (_cp > mf.length()) {
mf.setLength(_cp, false);
}
mf.setLastModified(System.currentTimeMillis());
}
} catch (BufferClosedException e) {
writeFile(bbuf, len, pos, offset);
} catch (Exception e) {
e.printStackTrace();
log.log(Level.SEVERE, "error while writing to " + this.mf.getPath()
+ " " + e.toString(), e);
// TODO : fix rollback
// df.rollBack();
this.close();
throw new IOException("error while writing to " + this.mf.getPath()
+ " " + e.toString());
} finally {
// this.removeAio();
}
}
/**
* writes data to the DedupFile at the current file position
*
* @param bbuf
* the bytes to write
* @param len
* the length of data to write
* @param offset
* the offset within the bbuf to start the write from
* @throws java.io.IOException
*/
public void writeFile(byte[] buf, int len, int pos)
throws java.io.IOException {
this.writeFile(buf, len, pos, this.position());
}
/**
* Reads data from the DedupFile at the current file position
*
* @param bbuf
* the byte array to copy the data to.
* @param bufPos
* the position within the array to copy the data too.
* @param siz
* the mount of data to copy to the bbuf.
* @return the bytes read
* @throws IOException
*/
public int read(byte[] bbuf, int bufPos, int siz) throws IOException {
return this.read(bbuf, bufPos, siz, this.position());
}
/**
* Closes the byte array
*
* @throws IOException
*/
public void close() throws IOException {
if (!this.isClosed()) {
this.closeLock.lock();
try {
if (this.writtenTo && Main.safeSync) {
df.writeCache();
mf.sync();
df.sync();
}
} catch (Exception e) {
} finally {
df.unRegisterChannel(this);
this.closed = true;
this.closeLock.unlock();
}
}
}
/**
* Reads data from the DedupFile
*
* @param bbuf
* the byte array to copy the data to.
* @param bufPos
* the position within the array to copy the data too.
* @param siz
* the mount of data to copy to the bbuf.
* @param filePos
* the position within the file to read the data from
* @return the bytes read
* @throws IOException
*/
public int read(byte[] bbuf, int bufPos, int siz, long filePos)
throws IOException {
// this.addAio();
try {
if (filePos >= mf.length()) {
return -1;
}
long currentLocation = filePos;
ByteBuffer buf = ByteBuffer.wrap(bbuf);
buf.position(bufPos);
int bytesLeft = siz;
long futureFilePostion = bytesLeft + currentLocation;
if (futureFilePostion > mf.length()) {
bytesLeft = (int) (mf.length() - currentLocation);
}
int read = 0;
while (bytesLeft > 0) {
DedupChunk readBuffer = null;
try {
readBuffer = df.getReadBuffer(currentLocation);
} catch (Exception e) {
// break;
throw new IOException("unable to read at [" + filePos
+ "] because [" + e.toString() + "]");
}
synchronized (readBuffer) {
int startPos = (int) (currentLocation - readBuffer
.getFilePosition());
int endPos = startPos + bytesLeft;
try {
if ((endPos) <= readBuffer.getLength()) {
buf.put(readBuffer.getChunk(), startPos, bytesLeft);
mf.getIOMonitor().addBytesRead(bytesLeft);
// log.finest("Read " + bytesLeft + " bytes");
read = read + bytesLeft;
bytesLeft = 0;
} else {
int _len = readBuffer.getLength() - startPos;
buf.put(readBuffer.getChunk(), startPos, _len);
mf.getIOMonitor().addBytesRead(_len);
currentLocation = currentLocation + _len;
bytesLeft = bytesLeft - _len;
read = read + _len;
}
}
catch (IOException e) {
log.log(Level.SEVERE, "Error while reading buffer ", e);
log.severe("Error Reading Buffer "
+ readBuffer.getHash() + " start position ["
+ startPos + "] " + "end position [" + endPos
+ "] bytes left [" + bytesLeft
+ "] filePostion [" + currentLocation + "] ");
this.close();
throw new IOException("Error reading buffer");
}
if (currentLocation == mf.length()) {
return read;
}
mf.setLastAccessed(System.currentTimeMillis());
this.currentPosition = currentLocation;
}
}
return read;
} catch (IOException e) {
throw e;
} finally {
// this.removeAio();
}
}
public DedupFile getDedupFile() {
return this.df;
}
/**
* Seek to the specified file position.
*
* @param pos
* long
* @param typ
* int
* @return long
* @exception IOException
*/
public long seekFile(long pos, int typ) throws IOException {
// Check if the current file position is the required file position
switch (typ) {
// From start of file
case SeekType.StartOfFile:
if (this.position() != pos)
this.currentPosition = pos;
break;
// From current position
case SeekType.CurrentPos:
this.currentPosition = this.currentPosition + pos;
break;
// From end of file
case SeekType.EndOfFile: {
this.currentPosition = pos;
}
break;
}
mf.setLastAccessed(System.currentTimeMillis());
// Return the new file position
return this.position();
}
/**
* Determine if the end of file has been reached.
*
* @return boolean
**/
public boolean isEndOfFile() throws java.io.IOException {
// Check if we reached end of file
if (this.currentPosition == mf.length())
return true;
return false;
}
/**
*
* @return the MetaDataDedupFile for this DedupFileChannel
* @throws IOException
*/
public MetaDataDedupFile openFile() throws IOException {
return this.mf;
}
/**
* Tries to lock a file at a specific position
*
* @param position
* the position to lock the file at.
* @param size
* the size of the data to be locked
* @param shared
* if the lock is shared or not
* @return true if it is locked
* @throws IOException
*/
public DedupFileLock tryLock(long position, long size, boolean shared)
throws IOException {
return df.addLock(this, position, size, shared);
}
/**
* Tries to lock a file exclusively
*
* @return true if the file is locked
* @throws IOException
*/
public DedupFileLock tryLock() throws IOException {
return df.addLock(this, 0, mf.length(), false);
}
/**
* Removes an existing lock on a file
*
* @param lock
* the lock on the file
*/
public void removeLock(DedupFileLock lock) {
lock.release();
df.removeLock(lock);
}
}