comparison src/com/tmate/hgkit/ll/RevlogStream.java @ 10:382cfe9463db

Dirstate parsing. DataAccess refactored to allow reuse and control over constants
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sat, 25 Dec 2010 21:50:12 +0100
parents d6d2a630f4a6
children 603806cd2dc6
comparison
equal deleted inserted replaced
9:d6d2a630f4a6 10:382cfe9463db
1 /** 1 /*
2 * Copyright (c) 2010 Artem Tikhomirov 2 * Copyright (c) 2010 Artem Tikhomirov
3 */ 3 */
4 package com.tmate.hgkit.ll; 4 package com.tmate.hgkit.ll;
5 5
6 import static com.tmate.hgkit.ll.HgRepository.TIP; 6 import static com.tmate.hgkit.ll.HgRepository.TIP;
7 7
8 import java.io.File; 8 import java.io.File;
9 import java.io.FileInputStream;
10 import java.io.IOException; 9 import java.io.IOException;
11 import java.nio.ByteBuffer;
12 import java.nio.MappedByteBuffer;
13 import java.nio.channels.FileChannel;
14 import java.util.ArrayList; 10 import java.util.ArrayList;
15 import java.util.Collections; 11 import java.util.Collections;
16 import java.util.LinkedList; 12 import java.util.LinkedList;
17 import java.util.List; 13 import java.util.List;
18 import java.util.zip.DataFormatException; 14 import java.util.zip.DataFormatException;
19 import java.util.zip.Inflater; 15 import java.util.zip.Inflater;
16
17 import com.tmate.hgkit.fs.DataAccess;
18 import com.tmate.hgkit.fs.DataAccessProvider;
20 19
21 /** 20 /**
22 * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), 21 * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations),
23 * or numerous RevlogStream with separate representation of the underlaying data (cached, lazy ChunkStream)? 22 * or numerous RevlogStream with separate representation of the underlaying data (cached, lazy ChunkStream)?
24 * @author artem 23 * @author artem
28 public class RevlogStream { 27 public class RevlogStream {
29 28
30 private List<IndexEntry> index; // indexed access highly needed 29 private List<IndexEntry> index; // indexed access highly needed
31 private boolean inline = false; 30 private boolean inline = false;
32 private final File indexFile; 31 private final File indexFile;
33 32 private final DataAccessProvider dataAccess;
34 RevlogStream(File indexFile) { 33
34 // if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP.
35 RevlogStream(DataAccessProvider dap, File indexFile) {
36 this.dataAccess = dap;
35 this.indexFile = indexFile; 37 this.indexFile = indexFile;
36 } 38 }
37 39
38 /*package*/ DataAccess getIndexStream() { 40 /*package*/ DataAccess getIndexStream() {
39 return create(indexFile); 41 return dataAccess.create(indexFile);
40 } 42 }
41 43
42 /*package*/ DataAccess getDataStream() { 44 /*package*/ DataAccess getDataStream() {
43 final String indexName = indexFile.getName(); 45 final String indexName = indexFile.getName();
44 File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d"); 46 File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d");
45 return create(dataFile); 47 return dataAccess.create(dataFile);
46 } 48 }
47 49
48 private DataAccess create(File f) {
49 if (!f.exists()) {
50 return new DataAccess();
51 }
52 try {
53 FileChannel fc = new FileInputStream(f).getChannel();
54 final int MAPIO_MAGIC_BOUNDARY = 100 * 1024;
55 if (fc.size() > MAPIO_MAGIC_BOUNDARY) {
56 return new MemoryMapFileAccess(fc, fc.size());
57 } else {
58 return new FileAccess(fc, fc.size());
59 }
60 } catch (IOException ex) {
61 // unlikely to happen, we've made sure file exists.
62 ex.printStackTrace(); // FIXME log error
63 }
64 return new DataAccess(); // non-null, empty.
65 }
66
67 public int revisionCount() { 50 public int revisionCount() {
68 initOutline(); 51 initOutline();
69 return index.size(); 52 return index.size();
70 } 53 }
71 54
218 da.skip(3*4 + 32 + compressedLen); // Check: 44 (skip) + 20 (read) = 64 (total RevlogNG record size) 201 da.skip(3*4 + 32 + compressedLen); // Check: 44 (skip) + 20 (read) = 64 (total RevlogNG record size)
219 } else { 202 } else {
220 res.add(new IndexEntry(offset, baseRevision)); 203 res.add(new IndexEntry(offset, baseRevision));
221 da.skip(3*4 + 32); 204 da.skip(3*4 + 32);
222 } 205 }
223 if (da.nonEmpty()) { 206 if (da.isEmpty()) {
224 long l = da.readLong();
225 offset = l >>> 16;
226 } else {
227 // fine, done then 207 // fine, done then
228 index = res; 208 index = res;
229 break; 209 break;
210 } else {
211 // start reading next record
212 long l = da.readLong();
213 offset = l >>> 16;
230 } 214 }
231 } 215 }
232 } catch (IOException ex) { 216 } catch (IOException ex) {
233 ex.printStackTrace(); // log error 217 ex.printStackTrace(); // log error
234 // too bad, no outline then. 218 // too bad, no outline then.
282 this.len = len; 266 this.len = len;
283 data = new byte[len]; 267 data = new byte[len];
284 System.arraycopy(src, srcOffset, data, 0, len); 268 System.arraycopy(src, srcOffset, data, 0, len);
285 } 269 }
286 } 270 }
287
288 /*package-local*/ class DataAccess {
289 public boolean nonEmpty() {
290 return false;
291 }
292 // absolute positioning
293 public void seek(long offset) throws IOException {
294 throw new UnsupportedOperationException();
295 }
296 // relative positioning
297 public void skip(int bytes) throws IOException {
298 throw new UnsupportedOperationException();
299 }
300 // shall be called once this object no longer needed
301 public void done() {
302 // no-op in this empty implementation
303 }
304 public int readInt() throws IOException {
305 byte[] b = new byte[4];
306 readBytes(b, 0, 4);
307 return b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF);
308 }
309 public long readLong() throws IOException {
310 byte[] b = new byte[8];
311 readBytes(b, 0, 8);
312 int i1 = b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF);
313 int i2 = b[4] << 24 | (b[5] & 0xFF) << 16 | (b[6] & 0xFF) << 8 | (b[7] & 0xFF);
314 return ((long) i1) << 32 | ((long) i2 & 0xFFFFFFFF);
315 }
316 public void readBytes(byte[] buf, int offset, int length) throws IOException {
317 throw new UnsupportedOperationException();
318 }
319 }
320
321 // DOESN'T WORK YET
322 private class MemoryMapFileAccess extends DataAccess {
323 private FileChannel fileChannel;
324 private final long size;
325 private long position = 0;
326
327 public MemoryMapFileAccess(FileChannel fc, long channelSize) {
328 fileChannel = fc;
329 size = channelSize;
330 }
331
332 @Override
333 public void seek(long offset) {
334 position = offset;
335 }
336
337 @Override
338 public void skip(int bytes) throws IOException {
339 position += bytes;
340 }
341
342 private boolean fill() throws IOException {
343 final int BUFFER_SIZE = 8 * 1024;
344 long left = size - position;
345 MappedByteBuffer rv = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, left < BUFFER_SIZE ? left : BUFFER_SIZE);
346 position += rv.capacity();
347 return rv.hasRemaining();
348 }
349
350 @Override
351 public void done() {
352 if (fileChannel != null) {
353 try {
354 fileChannel.close();
355 } catch (IOException ex) {
356 ex.printStackTrace(); // log debug
357 }
358 fileChannel = null;
359 }
360 }
361 }
362
363 private class FileAccess extends DataAccess {
364 private FileChannel fileChannel;
365 private final long size;
366 private ByteBuffer buffer;
367 private long bufferStartInFile = 0; // offset of this.buffer in the file.
368
369 public FileAccess(FileChannel fc, long channelSize) {
370 fileChannel = fc;
371 size = channelSize;
372 final int BUFFER_SIZE = 8 * 1024;
373 // XXX once implementation is more or less stable,
374 // may want to try ByteBuffer.allocateDirect() to see
375 // if there's any performance gain.
376 buffer = ByteBuffer.allocate(size < BUFFER_SIZE ? (int) size : BUFFER_SIZE);
377 buffer.flip(); // or .limit(0) to indicate it's empty
378 }
379
380 @Override
381 public boolean nonEmpty() {
382 return bufferStartInFile + buffer.position() < size;
383 }
384
385 @Override
386 public void seek(long offset) throws IOException {
387 if (offset < bufferStartInFile + buffer.limit() && offset >= bufferStartInFile) {
388 buffer.position((int) (offset - bufferStartInFile));
389 } else {
390 // out of current buffer, invalidate it (force re-read)
391 // XXX or ever re-read it right away?
392 bufferStartInFile = offset;
393 buffer.clear();
394 buffer.limit(0); // or .flip() to indicate we switch to reading
395 fileChannel.position(offset);
396 }
397 }
398
399 @Override
400 public void skip(int bytes) throws IOException {
401 final int newPos = buffer.position() + bytes;
402 if (newPos >= 0 && newPos < buffer.limit()) {
403 // no need to move file pointer, just rewind/seek buffer
404 buffer.position(newPos);
405 } else {
406 //
407 seek(fileChannel.position()+ bytes);
408 }
409 }
410
411 private boolean fill() throws IOException {
412 if (!buffer.hasRemaining()) {
413 bufferStartInFile += buffer.limit();
414 buffer.clear();
415 if (bufferStartInFile < size) { // just in case there'd be any exception on EOF, not -1
416 fileChannel.read(buffer);
417 // may return -1 when EOF, but empty will reflect this, hence no explicit support here
418 }
419 buffer.flip();
420 }
421 return buffer.hasRemaining();
422 }
423
424 @Override
425 public void readBytes(byte[] buf, int offset, int length) throws IOException {
426 final int tail = buffer.remaining();
427 if (tail >= length) {
428 buffer.get(buf, offset, length);
429 } else {
430 buffer.get(buf, offset, tail);
431 if (fill()) {
432 buffer.get(buf, offset + tail, length - tail);
433 } else {
434 throw new IOException(); // shall not happen provided stream contains expected data and no attempts to read past nonEmpty() == false are made.
435 }
436 }
437 }
438
439 @Override
440 public void done() {
441 if (buffer != null) {
442 buffer = null;
443 }
444 if (fileChannel != null) {
445 try {
446 fileChannel.close();
447 } catch (IOException ex) {
448 ex.printStackTrace(); // log debug
449 }
450 fileChannel = null;
451 }
452 }
453 }
454 } 271 }