package challengetask.group02.controllers;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.util.UUID;
//import java.util.zip.CRC32;
//import challengetask.group02.controllers.exceptions.CRCException;
import challengetask.group02.helpers.SimpleCache;
import net.tomp2p.dht.PeerDHT;
import net.tomp2p.peers.Number160;
import challengetask.group02.fsstructure.Block;
import challengetask.group02.fsstructure.File;
import challengetask.group02.helpers.FSModifyHelper;
import challengetask.group02.controllers.exceptions.BusyException;
//This class is used to split up the files and also fetch them
public class FileContentController implements IFileContentController {
private PeerDHT peer;
private FSModifyHelper dhtPutGetHelper;
private HashMap<String, byte[]> writeBuffer;
private HashMap<String, Number160> writeBufferBlockId;
private HashMap<String, Integer> writeBufferCounter;
public FileContentController(PeerDHT peer) {
//writeBuffer = new byte[File.BLOCK_SIZE];
//writeBufferBlockId = null;
//writeBufferCounter = 0;
writeBuffer = new HashMap<>();
writeBufferBlockId = new HashMap<>();
writeBufferCounter = new HashMap<>();
this.peer = peer;
dhtPutGetHelper = new FSModifyHelper(this.peer);
}
@Override
public int writeFile(File file, ByteBuffer buffer, long bufSize, long writeOffset, SimpleCache<File> cache) throws BusyException {
if (writeBuffer.get(file.getEntryName()) == null) {
writeBuffer.put(file.getEntryName(), new byte[File.BLOCK_SIZE]);
writeBufferBlockId.put(file.getEntryName(), null);
writeBufferCounter.put(file.getEntryName(), 0);
}
if (file.getReadOnly() == true) {
if (file.getModifierPeer().compareTo(peer.peerID()) != 0) {
throw new BusyException(file.getEntryName() + " is busy and held by peer with ID: " + file.getModifierPeer());
}
}
Random random = new Random();
byte[] content = new byte[(int) bufSize];
long outWriteOffset = writeOffset;
int startBlock = (int) (writeOffset / File.BLOCK_SIZE);
int endBlock = (int) ((bufSize + writeOffset - 1) / File.BLOCK_SIZE);
//copy the bytebuffer (data to write) into our content array
//assuming we the content has length bufSize, otherwise BufferUnderFlowException will be thrown
buffer.get(content);
ArrayList<Number160> blockIDs = file.getBlocks();
int blockCount = blockIDs.size();
//first block is special case
int startBytes = 0;
//last block is special case
int endBytes = 0;
//maintains the pointer where to read/write
int position = 0;
//had to add this because problems arise when we have a File.BLOCK_SIZE bigger than typical write size
boolean blockCreated = false;
for (int index = startBlock; index <= endBlock; index++) {
// CRC32 crc32 = new CRC32();
Block block;
int bytesToWrite = 0;
//if bufferBlockId == null then get block from dht and put it inside buffer
//if the block do not exists, create block, append to file, write file, write block, update file cache
//if the block doesn't exist, create a new one
if (writeBufferBlockId.get(file.getEntryName()) == null) {
if (index > blockCount - 1) {
Number160 ID = Number160.createHash(UUID.randomUUID().hashCode());
block = new Block();
// block.setChecksum(index);
block.setID(ID);
blockCount++;
blockCreated = true;
} else {
//if the block exists, fetch it
block = dhtPutGetHelper.getBlockDHT(blockIDs.get(index));
blockCreated = false;
}
writeBufferBlockId.put(file.getEntryName(), block.getID());
if (blockCreated == false) {
writeBuffer.put(file.getEntryName(), block.getData().clone());
}
} else {
block = new Block();
block.setID(writeBufferBlockId.get(file.getEntryName()));
}
//we have to make a distinction between which block we are visiting at the moment
if (startBlock == endBlock) {
bytesToWrite = (int) bufSize;
} else {
if (index == startBlock) {
startBytes = File.BLOCK_SIZE - (int) writeOffset % File.BLOCK_SIZE;
bytesToWrite = startBytes;
} else if (index == endBlock) {
endBytes = (int) ((bufSize + outWriteOffset) % File.BLOCK_SIZE);
if (endBytes == 0) endBytes = File.BLOCK_SIZE;
bytesToWrite = endBytes;
} else {
bytesToWrite = File.BLOCK_SIZE;
}
}
System.arraycopy(content, position, writeBuffer.get(file.getEntryName()), (int) writeOffset % File.BLOCK_SIZE, bytesToWrite);
writeBufferCounter.put(file.getEntryName(), (int) writeOffset % File.BLOCK_SIZE + bytesToWrite);
if (writeBufferCounter.get(file.getEntryName()) == File.BLOCK_SIZE) {
block.setData(writeBuffer.get(file.getEntryName()).clone());
dhtPutGetHelper.putBlock(block.getID(), block);
writeBufferCounter.put(file.getEntryName(), 0);
writeBufferBlockId.put(file.getEntryName(), null);
//writeBufferBlockId = null;
}
if (blockCreated) {
file.addBlock(block.getID());
}
//offset is only for the first time
writeOffset = 0;
position += bytesToWrite;
}
file.setSize(position + outWriteOffset);
//update meta information
file.setCtime(System.currentTimeMillis() / 1000L);
file.setAtime(System.currentTimeMillis() / 1000L);
dhtPutGetHelper.putFile(file.getID(), file);
//the size of the content that was written
return position;
}
@Override
public byte[] readFile(File file, long size, long offset) /*throws CRCException */ {
//We don't need to read the whole file, but only "size" bytes, starting from "offset"
byte[] content = new byte[(int) size];
ArrayList<Number160> blocks = file.getBlocks();
//evaulate which block offset points to we assume the first block has index 0
//the first block spans from 0 - BLOCK_SIZE-1
//the second block from BLOCK_SIZE - 2*BLOCK_SIZE-1 etc.
int startBlock = (int) (offset / File.BLOCK_SIZE);
long outOffset = offset;
//how many blocks to read? depends on the position of the offset
//the -1 because the byte where the offset points to is read as well
//example offset "abcdefgh" with offset = 3 and length = 3 is "def"
int endBlock = (int) ((size + offset - 1) / File.BLOCK_SIZE);
if (endBlock > blocks.size() - 1) {
endBlock = blocks.size() - 1;
size = blocks.size() * File.BLOCK_SIZE;
}
//number of bytes read from the first block
int startBytes = 0;
//number of bytes read from the last block
int endBytes = 0;
int position = 0;
for (int index = startBlock; index <= endBlock; index++) {
Number160 ID = blocks.get(index);
Block block = dhtPutGetHelper.getBlockDHT(ID);
//CRC32 crc32 = new CRC32();
int bytesToRead = 0;
//first check if CRC is correct
/*crc32.update(block.getData());
if(! (crc32.getValue() == block.getChecksum()) ) {
throw new CRCException(file.getEntryName());
}
*/
//since we read sequential, sequence number checking doesn't make sense in this case
//we have to do case distinction, if we only have one block to read, it's easily done.
if (startBlock == endBlock) {
bytesToRead = (int) size;
} else {
//if it's the first block we're reading, we only have to start to read from offset
//last block is also special
if (index == startBlock) {
startBytes = File.BLOCK_SIZE - (int) offset % File.BLOCK_SIZE;
bytesToRead = startBytes;
} else if (index == endBlock) {
endBytes = (int) ((size + outOffset) % File.BLOCK_SIZE);
if (endBytes == 0) endBytes = File.BLOCK_SIZE;
bytesToRead = endBytes;
} else {
bytesToRead = File.BLOCK_SIZE;
}
}
System.arraycopy(block.getData(), (int) offset % File.BLOCK_SIZE, content, position, bytesToRead);
offset = 0;
position += bytesToRead;
}
//update meta information
file.setAtime(System.currentTimeMillis() / 1000L);
dhtPutGetHelper.putFile(file.getID(), file);
return content;
}
@Override
public void flush(String path, File file) {
if (writeBufferBlockId.get(file.getEntryName()) != null) {
Block block = new Block();
block.setID(writeBufferBlockId.get(file.getEntryName()));
block.setData(writeBuffer.get(file.getEntryName()).clone());
dhtPutGetHelper.putBlock(block.getID(), block);
writeBufferCounter.put(file.getEntryName(), 0);
writeBufferBlockId.put(file.getEntryName(), null);
//writeBufferBlockId = null;
}
}
}