/** * Calculate, add or merge rd5 delta files * * @author ab */ package btools.mapaccess; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; import btools.codec.DataBuffers; import btools.codec.MicroCache; import btools.codec.MicroCache2; import btools.codec.StatCoderContext; import btools.util.Crc32; import btools.util.ProgressListener; final public class Rd5DiffTool implements ProgressListener { public static void main(String[] args) throws Exception { if (args.length == 2) { reEncode(new File(args[0]), new File(args[1])); return; } if (args[1].endsWith(".df5")) { if (args[0].endsWith(".df5")) { addDeltas(new File(args[0]), new File(args[1]), new File(args[2])); } else { recoverFromDelta(new File(args[0]), new File(args[1]), new File(args[2]), new Rd5DiffTool() /*, new File( args[3] ) */); } } else { diff2files(new File(args[0]), new File(args[1]), new File(args[2])); } } @Override public void updateProgress(String task, int progress) { System.out.println(task + ": " + progress + "%"); } @Override public boolean isCanceled() { return false; } private static long[] readFileIndex(DataInputStream dis, DataOutputStream dos) throws IOException { long[] fileIndex = new long[25]; for (int i = 0; i < 25; i++) { long lv = dis.readLong(); fileIndex[i] = lv & 0xffffffffffffL; if (dos != null) { dos.writeLong(lv); } } return fileIndex; } private static long getTileStart(long[] index, int tileIndex) { return tileIndex > 0 ? index[tileIndex - 1] : 200L; } private static long getTileEnd(long[] index, int tileIndex) { return index[tileIndex]; } private static int[] readPosIndex(DataInputStream dis, DataOutputStream dos) throws IOException { int[] posIndex = new int[1024]; for (int i = 0; i < 1024; i++) { int iv = dis.readInt(); posIndex[i] = iv; if (dos != null) { dos.writeInt(iv); } } return posIndex; } private static int getPosIdx(int[] posIdx, int idx) { return idx == -1 ? 4096 : posIdx[idx]; } private static byte[] createMicroCache(int[] posIdx, int tileIdx, DataInputStream dis, boolean deltaMode) throws IOException { if (posIdx == null) { return null; } int size = getPosIdx(posIdx, tileIdx) - getPosIdx(posIdx, tileIdx - 1); if (size == 0) { return null; } if (deltaMode) { size = dis.readInt(); } byte[] ab = new byte[size]; dis.readFully(ab); return ab; } private static MicroCache createMicroCache(byte[] ab, DataBuffers dataBuffers) { if (ab == null || ab.length == 0) { return MicroCache.emptyCache(); } StatCoderContext bc = new StatCoderContext(ab); return new MicroCache2(bc, dataBuffers, 0, 0, 32, null, null); } /** * Compute the delta between 2 RD5 files and * show statistics on the expected size of the delta file */ public static void diff2files(File f1, File f2, File outFile) throws Exception { byte[] abBuf1 = new byte[10 * 1024 * 1024]; byte[] abBuf2 = new byte[10 * 1024 * 1024]; int nodesDiff = 0; int diffedTiles = 0; long bytesDiff = 0L; DataInputStream dis1 = new DataInputStream(new BufferedInputStream(new FileInputStream(f1))); DataInputStream dis2 = new DataInputStream(new BufferedInputStream(new FileInputStream(f2))); DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); MCOutputStream mcOut = new MCOutputStream(dos, abBuf1); // copy header to outfile long[] fileIndex1 = readFileIndex(dis1, null); long[] fileIndex2 = readFileIndex(dis2, dos); long t0 = System.currentTimeMillis(); try { DataBuffers dataBuffers = new DataBuffers(); for (int subFileIdx = 0; subFileIdx < 25; subFileIdx++) { boolean hasData1 = getTileStart(fileIndex1, subFileIdx) < getTileEnd(fileIndex1, subFileIdx); boolean hasData2 = getTileStart(fileIndex2, subFileIdx) < getTileEnd(fileIndex2, subFileIdx); int[] posIdx1 = hasData1 ? readPosIndex(dis1, null) : null; int[] posIdx2 = hasData2 ? readPosIndex(dis2, dos) : null; for (int tileIdx = 0; tileIdx < 1024; tileIdx++) { byte[] ab1 = createMicroCache(posIdx1, tileIdx, dis1, false); byte[] ab2 = createMicroCache(posIdx2, tileIdx, dis2, false); MicroCache mc; if (Arrays.equals(ab1, ab2)) { mc = MicroCache.emptyCache(); // empty diff } else // calc diff of the 2 tiles { MicroCache mc1 = createMicroCache(ab1, dataBuffers); MicroCache mc2 = createMicroCache(ab2, dataBuffers); mc = new MicroCache2(mc1.getSize() + mc2.getSize(), abBuf2, 0, 0, 32); mc.calcDelta(mc1, mc2); } int len = mcOut.writeMC(mc); if (len > 0) { bytesDiff += len; nodesDiff += mc.getSize(); diffedTiles++; /* // do some consistemcy checks on the encoding byte[] bytes = new byte[len]; System.arraycopy( abBuf1, 0, bytes, 0, len ); // cross-check the encoding: decode again MicroCache mcCheck = new MicroCache2( new StatCoderContext( bytes ), new DataBuffers( null ), 0, 0, 32, null, null ); // due to link-order ambiguity, for decoded we can only compare node-count and datasize if ( mc.size() != mcCheck.size() ) { throw new IllegalArgumentException( "re-decoded data-size mismatch!" ); } if ( mc.getSize() != mcCheck.getSize() ) { throw new IllegalArgumentException( "re-decoded node-count mismatch!" ); } // .... so re-encode again int len2 = mcCheck.encodeMicroCache( abBuf1 ); byte[] bytes2 = new byte[len2]; System.arraycopy( abBuf1, 0, bytes2, 0, len2 ); // and here we can compare byte-by-byte if ( len != len2 ) { throw new IllegalArgumentException( "decoded size mismatch!" ); } for( int i=0; i0"); } continue; } int len = mc.encodeMicroCache(abBuf1); /* System.out.println( "comparing for subFileIdx=" + subFileIdx + " tileIdx=" + tileIdx ); boolean isequal = true; for( int i=0; i" + (len + 4)); } } mcIn.finish(); } // write any remaining data to the output file for (; ; ) { int len = dis2.read(abBuf1); if (len < 0) { break; } dos.write(abBuf1, 0, len); } long t1 = System.currentTimeMillis(); System.out.println("recovering from diffs took " + (t1 - t0) + "ms"); } finally { if (dis1 != null) { try { dis1.close(); } catch (Exception ee) { } } if (dis2 != null) { try { dis2.close(); } catch (Exception ee) { } } if (dos != null) { try { dos.close(); } catch (Exception ee) { } if (canceled) { outFile.delete(); } } } } public static void copyFile(File f1, File outFile, ProgressListener progress) throws IOException { boolean canceled = false; DataInputStream dis1 = new DataInputStream(new BufferedInputStream(new FileInputStream(f1))); DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); int lastPct = -1; long sizeTotal = f1.length(); long sizeRead = 0L; try { byte buf[] = new byte[65536]; for (; ; ) { if (progress.isCanceled()) { canceled = true; return; } int pct = (int) ((100. * sizeRead) / (sizeTotal + 1) + 0.5); if (pct != lastPct) { progress.updateProgress("Copying", pct); lastPct = pct; } int len = dis1.read(buf); if (len <= 0) { break; } sizeRead += len; dos.write(buf, 0, len); } } finally { if (dis1 != null) { try { dis1.close(); } catch (Exception ee) { } } if (dos != null) { try { dos.close(); } catch (Exception ee) { } if (canceled) { outFile.delete(); } } } } public static void addDeltas(File f1, File f2, File outFile) throws Exception { byte[] abBuf1 = new byte[10 * 1024 * 1024]; byte[] abBuf2 = new byte[10 * 1024 * 1024]; DataInputStream dis1 = new DataInputStream(new BufferedInputStream(new FileInputStream(f1))); DataInputStream dis2 = new DataInputStream(new BufferedInputStream(new FileInputStream(f2))); DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); // copy subfile-header to outfile long[] fileIndex1 = readFileIndex(dis1, null); long[] fileIndex2 = readFileIndex(dis2, dos); long t0 = System.currentTimeMillis(); try { DataBuffers dataBuffers = new DataBuffers(); MCInputStream mcIn1 = new MCInputStream(dis1, dataBuffers); MCInputStream mcIn2 = new MCInputStream(dis2, dataBuffers); MCOutputStream mcOut = new MCOutputStream(dos, abBuf1); for (int subFileIdx = 0; subFileIdx < 25; subFileIdx++) { // copy tile-header to outfile boolean hasData1 = getTileStart(fileIndex1, subFileIdx) < getTileEnd(fileIndex1, subFileIdx); boolean hasData2 = getTileStart(fileIndex2, subFileIdx) < getTileEnd(fileIndex2, subFileIdx); int[] posIdx1 = hasData1 ? readPosIndex(dis1, null) : null; int[] posIdx2 = hasData2 ? readPosIndex(dis2, dos) : null; for (int tileIdx = 0; tileIdx < 1024; tileIdx++) { MicroCache mc1 = mcIn1.readMC(); MicroCache mc2 = mcIn2.readMC(); MicroCache mc; if (mc1.getSize() == 0 && mc2.getSize() == 0) { mc = mc1; } else { mc = new MicroCache2(mc1.getSize() + mc2.getSize(), abBuf2, 0, 0, 32); mc.addDelta(mc1, mc2, true); } mcOut.writeMC(mc); } mcIn1.finish(); mcIn2.finish(); mcOut.finish(); } // write any remaining data to the output file for (; ; ) { int len = dis2.read(abBuf1); if (len < 0) { break; } dos.write(abBuf1, 0, len); } long t1 = System.currentTimeMillis(); System.out.println("adding diffs took " + (t1 - t0) + "ms"); } finally { if (dis1 != null) { try { dis1.close(); } catch (Exception ee) { } } if (dis2 != null) { try { dis2.close(); } catch (Exception ee) { } } if (dos != null) { try { dos.close(); } catch (Exception ee) { } } } } public static void reEncode(File f1, File outFile) throws Exception { byte[] abBuf1 = new byte[10 * 1024 * 1024]; DataInputStream dis1 = new DataInputStream(new BufferedInputStream(new FileInputStream(f1))); DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); // copy header to outfile long[] fileIndex1 = readFileIndex(dis1, dos); long t0 = System.currentTimeMillis(); try { DataBuffers dataBuffers = new DataBuffers(); for (int subFileIdx = 0; subFileIdx < 25; subFileIdx++) { boolean hasData1 = getTileStart(fileIndex1, subFileIdx) < getTileEnd(fileIndex1, subFileIdx); int[] posIdx1 = hasData1 ? readPosIndex(dis1, dos) : null; for (int tileIdx = 0; tileIdx < 1024; tileIdx++) { byte[] ab1 = createMicroCache(posIdx1, tileIdx, dis1, false); if (ab1 == null) continue; MicroCache mc1 = createMicroCache(ab1, dataBuffers); int len = mc1.encodeMicroCache(abBuf1); dos.write(abBuf1, 0, len); dos.writeInt(Crc32.crc(abBuf1, 0, len) ^ 2); } } // write any remaining data to the output file for (; ; ) { int len = dis1.read(abBuf1); if (len < 0) { break; } dos.write(abBuf1, 0, len); } long t1 = System.currentTimeMillis(); System.out.println("re-encoding took " + (t1 - t0) + "ms"); } finally { if (dis1 != null) { try { dis1.close(); } catch (Exception ee) { } } if (dos != null) { try { dos.close(); } catch (Exception ee) { } } } } private static class MCOutputStream { private DataOutputStream dos; private byte[] buffer; private short skips = 0; public MCOutputStream(DataOutputStream dos, byte[] buffer) { this.dos = dos; this.buffer = buffer; } public int writeMC(MicroCache mc) throws Exception { if (mc.getSize() == 0) { skips++; return 0; } dos.writeShort(skips); skips = 0; int len = mc.encodeMicroCache(buffer); if (len == 0) { throw new IllegalArgumentException("encoded buffer of non-empty micro-cache cannot be empty"); } dos.writeInt(len); dos.write(buffer, 0, len); return len; } public void finish() throws Exception { if (skips > 0) { dos.writeShort(skips); skips = 0; } } } private static class MCInputStream { private short skips = -1; private DataInputStream dis; private DataBuffers dataBuffers; private MicroCache empty = MicroCache.emptyCache(); public MCInputStream(DataInputStream dis, DataBuffers dataBuffers) { this.dis = dis; this.dataBuffers = dataBuffers; } public MicroCache readMC() throws IOException { if (skips < 0) { skips = dis.readShort(); } MicroCache mc = empty; if (skips == 0) { int size = dis.readInt(); byte[] ab = new byte[size]; dis.readFully(ab); StatCoderContext bc = new StatCoderContext(ab); mc = new MicroCache2(bc, dataBuffers, 0, 0, 32, null, null); } skips--; return mc; } public void finish() { skips = -1; } } }