/* MetafileReader.java
MetafileReader: Metafile reader class
Copyright (C) 2011 Tomáš Hlavnička <hlavntom@fel.cvut.cz>
This file is a part of Jazsync.
Jazsync is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2 of the License, or (at
your option) any later version.
Jazsync is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jazsync; if not, write to the
Free Software Foundation, Inc.,
59 Temple Place, Suite 330,
Boston, MA 02111-1307
USA
*/
package com.ettrema.zsync;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import com.ettrema.zsync.HeaderMaker.Headers;
import com.ettrema.zsync.MetaFileMaker.MetaData;
/**
* Class used to read metafile
* @author Tomáš Hlavnička
*/
public class MetaFileReader {
private ChainingHash hashtable;
private int fileOffset;
private int blockNum;
/** Variables for header information from .zsync metafile */
//------------------------------
private Headers headers;
public MetaFileReader(File metafile) {
readMetaFile(metafile);
blockNum = (int) Math.ceil((double) headers.length / (double) headers.blocksize);
readChecksums(metafile) ;
}
public MetaFileReader(MetaData metaData) {
this.headers = metaData.getHeaders();
blockNum = (int) Math.ceil((double) headers.length / (double) headers.blocksize);
fillHashTable(metaData.getChecksums());
}
/**
* Parsing method for metafile headers, saving each value into separate variable.
* @param s String containing metafile
* @return Boolean value notifying whether header ended or not (true = end of header)
*/
private boolean parseHeader(String s) {
String subs;
int colonIndex;
if (s.equals("")) {
//timto prazdnym radkem skoncil header, muzeme prestat cist
return true;
}
colonIndex = s.indexOf(":");
subs = s.substring(0, colonIndex);
if (subs.equalsIgnoreCase("zsync")) {
headers.version = s.substring(colonIndex + 2);
//zkontrolujeme kompatibilitu
if (headers.version.equals("0.0.4") || headers.version.equals("0.0.2")) {
throw new RuntimeException("This version is not compatible with zsync streams in versions up to 0.0.4");
}
} else if (subs.equalsIgnoreCase("Blocksize")) {
headers.blocksize = Integer.parseInt(s.substring(colonIndex + 2));
} else if (subs.equalsIgnoreCase("Length")) {
headers.length = Long.parseLong(s.substring(colonIndex + 2));
} else if (subs.equalsIgnoreCase("Hash-Lengths")) {
int comma = s.indexOf(",");
int seqNum = Integer.parseInt(s.substring((colonIndex + 2), comma));
headers.setSeqNum(seqNum);
int nextComma = s.indexOf(",", comma + 1);
headers.setRsumBytes( Integer.parseInt(s.substring(comma + 1, nextComma)) );
headers.setChecksumBytes( Integer.parseInt(s.substring(nextComma + 1)) );
if ((headers.getSeqNum() < 1 || headers.getSeqNum() > 2)
|| (headers.getRsumButes() < 1 || headers.getRsumButes() > 4)
|| (headers.getChecksumBytes() < 3 || headers.getChecksumBytes() > 16)) {
throw new RuntimeException("Nonsensical hash lengths line " + s.substring(colonIndex + 2));
}
} else if (subs.equalsIgnoreCase("URL")) {
headers.url = s.substring(colonIndex + 2);
} else if (subs.equalsIgnoreCase("Z-URL")) {
//not implemented yet
} else if (subs.equalsIgnoreCase("SHA-1")) {
headers.sha1 = s.substring(colonIndex + 2);
} else if (subs.equalsIgnoreCase("Z-Map2")) {
//not implemented yet
}
return false;
}
/**
* Method reads metafile from file and reads
* it line by line, sending line String to parser.
*/
private void readMetaFile(File metafile) {
headers = new Headers();
try {
BufferedReader in = new BufferedReader(new FileReader(metafile));
String s;
while ((s = in.readLine()) != null) {
if (parseHeader(s)) {
break;
}
}
in.close();
} catch (IOException e) {
throw new RuntimeException("IO problem in metafile header reading", e);
}
}
/**
* Method that reads metafile from file and stores its content into byte array
* and saves offset where headers end and blocksums starts.
*/
private void readChecksums(File metafile) {
long length = metafile.length();
if (metafile.length() > Integer.MAX_VALUE) {
throw new RuntimeException("Metafile is too large");
}
byte[] bytes = new byte[(int) length];
try {
InputStream is = new FileInputStream(metafile);
int offset = 0;
int n = 0;
while (offset < bytes.length && (n = is.read(bytes, offset, bytes.length - offset)) >= 0) {
offset += n;
}
// Presvedcime se, ze jsme precetli cely soubor
if (offset < bytes.length) {
throw new IOException("Could not completely read file " + metafile.getName());
}
is.close();
} catch (IOException e) {
throw new RuntimeException("IO problem in metafile reading", e);
}
// urci offset, kde konci hlavicka a zacinaji kontrolni soucty
fileOffset = 0;
for (int i = 2; i < bytes.length; i++) {
if (bytes[i - 2] == 10 && bytes[i - 1] == 10) {
fileOffset = i;
break;
}
}
fillHashTable(bytes);
}
private void fillHashTable(List<ChecksumPair> list) {
int i = 16;
//spocteme velikost hashtable podle poctu bloku dat
while ((2 << (i - 1)) > blockNum && i > 4) {
i--;
}
//vytvorime hashtable o velikosti 2^i (max. 2^16, min. 2^4)
hashtable = new ChainingHash(2 << (i - 1));
for( ChecksumPair pair : list ) {
hashtable.insert(pair);
}
}
/**
* Fills a chaining hash table with ChecksumPairs
* @param checksums Byte array with bytes of whole metafile
*/
private void fillHashTable(byte[] checksums) {
int i = 16;
//spocteme velikost hashtable podle poctu bloku dat
while ((2 << (i - 1)) > blockNum && i > 4) {
i--;
}
//vytvorime hashtable o velikosti 2^i (max. 2^16, min. 2^4)
hashtable = new ChainingHash(2 << (i - 1));
ChecksumPair p = null;
//Link item;
int offset = 0;
int weakSum = 0;
int seq = 0;
int off = fileOffset;
byte[] weak = new byte[4];
byte[] strongSum = new byte[headers.getChecksumBytes()];
while (seq < blockNum) {
for (int w = 0; w < headers.getRsumButes(); w++) {
weak[w] = checksums[off];
off++;
}
for (int s = 0; s < strongSum.length; s++) {
strongSum[s] = checksums[off];
off++;
}
weakSum = 0;
weakSum += (weak[2] & 0x000000FF) << 24;
weakSum += (weak[3] & 0x000000FF) << 16;
weakSum += (weak[0] & 0x000000FF) << 8;
weakSum += (weak[1] & 0x000000FF);
p = new ChecksumPair(weakSum, strongSum.clone(), offset, headers.blocksize, seq);
offset += headers.blocksize;
seq++;
//item = new Link(p);
hashtable.insert(p);
}
}
/**
* Returns hash table cotaining block checksums
* @return Hash table
*/
public ChainingHash getHashtable() {
return hashtable;
}
/**
* Returns number of blocks in complete file
* @return Number of blocks
*/
public int getBlockCount() {
return blockNum;
}
/**
* Returns size of block
* @return Size of the data block
*/
public int getBlocksize() {
return headers.blocksize;
}
/**
* Length of used strong sum
* @return Length of strong sum
*/
public int getChecksumBytes() {
return headers.getChecksumBytes();
}
/**
* Returns length of complete file
* @return Length of the file
*/
public long getLength() {
return headers.length;
}
/**
* Length of used weak sum
* @return Length of weak sum
*/
public int getRsumBytes() {
return headers.getRsumButes();
}
/**
* Number of consequence blocks
* @return Number of consequence blocks
*/
public int getSeqNum() {
return headers.getSeqNum();
}
/**
* Returns SHA1sum of complete file
* @return String containing SHA1 sum of complete file
*/
public String getSha1() {
return headers.sha1;
}
}