tikhomirov@0: /** tikhomirov@0: * Copyright (c) 2010 Artem Tikhomirov tikhomirov@0: */ tikhomirov@0: package com.tmate.hgkit.ll; tikhomirov@0: tikhomirov@0: import java.io.DataInput; tikhomirov@2: import java.io.EOFException; tikhomirov@2: import java.io.IOException; tikhomirov@2: import java.util.ArrayList; tikhomirov@2: import java.util.Collections; tikhomirov@2: import java.util.List; tikhomirov@2: import java.util.zip.Inflater; tikhomirov@0: tikhomirov@0: /** tikhomirov@0: * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), tikhomirov@0: * or numerous RevlogStream with separate representation of the underlaying data (cached, lazy ChunkStream)? tikhomirov@0: * @author artem tikhomirov@0: * @see http://mercurial.selenic.com/wiki/Revlog tikhomirov@0: * @see http://mercurial.selenic.com/wiki/RevlogNG tikhomirov@0: */ tikhomirov@0: public class RevlogStream { tikhomirov@2: tikhomirov@2: private List index; // indexed access highly needed tikhomirov@2: private boolean inline = false; tikhomirov@2: tikhomirov@0: private void detectVersion() { tikhomirov@0: tikhomirov@0: } tikhomirov@0: tikhomirov@0: /*package*/ DataInput getIndexStream() { tikhomirov@0: // TODO Auto-generated method stub tikhomirov@0: return null; tikhomirov@0: } tikhomirov@0: tikhomirov@0: /*package*/ DataInput getDataStream() { tikhomirov@0: // TODO Auto-generated method stub tikhomirov@0: return null; tikhomirov@0: } tikhomirov@2: public int revisionCount() { tikhomirov@2: initOutline(); tikhomirov@2: return index.size(); tikhomirov@2: } tikhomirov@2: tikhomirov@2: // should be possible to use TIP, ALL tikhomirov@2: public void iterate(int start, int end, boolean needData, Revlog.Inspector inspector) { tikhomirov@2: initOutline(); tikhomirov@2: final int indexSize = index.size(); tikhomirov@2: if (start < 0 || start >= indexSize) { tikhomirov@2: throw new IllegalArgumentException("Bad left range boundary " + start); tikhomirov@2: } tikhomirov@2: if (end < start || end >= indexSize) { tikhomirov@2: throw new IllegalArgumentException("Bad right range boundary " + end); tikhomirov@2: } tikhomirov@2: // XXX may cache [start .. end] from index with a single read (pre-read) tikhomirov@2: tikhomirov@2: DataInput diIndex = null, diData = null; tikhomirov@2: diIndex = getIndexStream(); tikhomirov@2: if (needData) { tikhomirov@2: diData = getDataStream(); tikhomirov@2: } tikhomirov@2: try { tikhomirov@2: diIndex.skipBytes(inline ? (int) index.get(start).offset : start * 64); tikhomirov@2: for (int i = start; i <= end && i < indexSize; i++ ) { tikhomirov@2: IndexEntry ie = index.get(i); tikhomirov@2: long l = diIndex.readLong(); tikhomirov@2: long offset = l >>> 16; tikhomirov@2: int flags = (int) (l & 0X0FFFF); tikhomirov@2: int compressedLen = diIndex.readInt(); tikhomirov@2: int actualLen = diIndex.readInt(); tikhomirov@2: int baseRevision = diIndex.readInt(); tikhomirov@2: int linkRevision = diIndex.readInt(); tikhomirov@2: int parent1Revision = diIndex.readInt(); tikhomirov@2: int parent2Revision = diIndex.readInt(); tikhomirov@2: byte[] buf = new byte[32]; tikhomirov@2: // XXX Hg keeps 12 last bytes empty, we move them into front here tikhomirov@2: diIndex.readFully(buf, 12, 20); tikhomirov@2: diIndex.skipBytes(12); tikhomirov@2: byte[] data = null; tikhomirov@2: if (needData) { tikhomirov@2: byte[] dataBuf = new byte[compressedLen]; tikhomirov@2: if (inline) { tikhomirov@2: diIndex.readFully(dataBuf); tikhomirov@2: } else { tikhomirov@2: diData.skipBytes((int) ie.offset); // FIXME not skip but seek!!! tikhomirov@2: diData.readFully(dataBuf); tikhomirov@2: } tikhomirov@2: if (dataBuf[0] == 0x78 /* 'x' */) { tikhomirov@2: Inflater zlib = new Inflater(); tikhomirov@2: zlib.setInput(dataBuf, 0, compressedLen); tikhomirov@2: byte[] result = new byte[actualLen*2]; // FIXME need to use zlib.finished() instead tikhomirov@2: int resultLen = zlib.inflate(result); tikhomirov@2: zlib.end(); tikhomirov@2: data = new byte[resultLen]; tikhomirov@2: System.arraycopy(result, 0, data, 0, resultLen); tikhomirov@2: } else if (dataBuf[0] == 0x75 /* 'u' */) { tikhomirov@2: data = new byte[dataBuf.length - 1]; tikhomirov@2: System.arraycopy(dataBuf, 1, data, 0, data.length); tikhomirov@2: } else { tikhomirov@2: // XXX Python impl in fact throws exception when there's not 'x', 'u' or '0' tikhomirov@2: // but I don't see reason not to just return data as is tikhomirov@2: data = dataBuf; tikhomirov@2: } tikhomirov@2: // FIXME if patch data (based on baseRevision), then apply patches to get true content tikhomirov@2: } tikhomirov@2: inspector.next(compressedLen, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, buf, data); tikhomirov@2: } tikhomirov@2: } catch (EOFException ex) { tikhomirov@2: // should not happen as long as we read inside known boundaries tikhomirov@2: throw new IllegalStateException(ex); tikhomirov@2: } catch (IOException ex) { tikhomirov@2: FIXME tikhomirov@2: } tikhomirov@2: } tikhomirov@0: tikhomirov@2: private void initOutline() { tikhomirov@2: if (index != null && !index.isEmpty()) { tikhomirov@2: return; tikhomirov@2: } tikhomirov@2: ArrayList res = new ArrayList(); tikhomirov@2: DataInput di = getIndexStream(); tikhomirov@2: try { tikhomirov@2: int versionField = di.readInt(); tikhomirov@2: // di.undreadInt(); tikhomirov@2: final int INLINEDATA = 1 << 16; tikhomirov@2: inline = (versionField & INLINEDATA) != 0; tikhomirov@2: long offset = 0; // first offset is always 0, thus Hg uses it for other purposes tikhomirov@2: while(true) { // EOFExcepiton should get us outta here. FIXME Out inputstream should has explicit no-more-data indicator tikhomirov@2: int compressedLen = di.readInt(); tikhomirov@2: // 8+4 = 12 bytes total read tikhomirov@2: // int actualLen = di.readInt(); tikhomirov@2: // int baseRevision = di.readInt(); tikhomirov@2: // int linkRevision = di.readInt(); tikhomirov@2: // int parent1Revision = di.readInt(); tikhomirov@2: // int parent2Revision = di.readInt(); tikhomirov@2: // byte[] nodeid = new byte[32]; tikhomirov@2: res.add(new IndexEntry(offset, compressedLen)); tikhomirov@2: if (inline) { tikhomirov@2: di.skipBytes(6*4 + 32 + compressedLen); // Check: 56 (skip) + 12 (read) = 64 (total RevlogNG record size) tikhomirov@2: } else { tikhomirov@2: di.skipBytes(6*4 + 32); tikhomirov@2: } tikhomirov@2: long l = di.readLong(); tikhomirov@2: offset = l >>> 16; tikhomirov@2: } tikhomirov@2: } catch (EOFException ex) { tikhomirov@2: // fine, done then tikhomirov@2: index = res; tikhomirov@2: } catch (IOException ex) { tikhomirov@2: ex.printStackTrace(); tikhomirov@2: // too bad, no outline then tikhomirov@2: index = Collections.emptyList(); tikhomirov@2: } tikhomirov@2: } tikhomirov@2: tikhomirov@2: tikhomirov@2: // perhaps, package-local or protected, if anyone else from low-level needs them tikhomirov@2: private static class IndexEntry { tikhomirov@2: public final long offset; tikhomirov@2: public final int length; // data past fixed record (need to decide whether including header size or not), and whether length is of compressed data or not tikhomirov@2: tikhomirov@2: public IndexEntry(long o, int l) { tikhomirov@2: offset = o; tikhomirov@2: length = l; tikhomirov@2: } tikhomirov@2: } tikhomirov@0: }