Mercurial > jhg
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 } |
