From 354cef3a9c4e020aef9afeee12f846e4554ec7b7 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Fri, 19 Oct 2012 10:53:02 +0400 Subject: add rarlib stuff --- src/org/catacombae/rarx/RARFileEntryStream.java | 550 ++++++++++++++++++++++++ 1 file changed, 550 insertions(+) create mode 100644 src/org/catacombae/rarx/RARFileEntryStream.java (limited to 'src/org/catacombae/rarx/RARFileEntryStream.java') diff --git a/src/org/catacombae/rarx/RARFileEntryStream.java b/src/org/catacombae/rarx/RARFileEntryStream.java new file mode 100644 index 0000000..c632900 --- /dev/null +++ b/src/org/catacombae/rarx/RARFileEntryStream.java @@ -0,0 +1,550 @@ +/*- + * Copyright (C) 2006 Erik Larsson + * + * All rights reserved. + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package org.catacombae.rarx; + +import org.catacombae.io.*; +import java.io.*; +import java.util.LinkedList; +import java.util.regex.Pattern; + +public class RARFileEntryStream implements RandomAccessStream { + public static class InvalidRARFilenameException extends RuntimeException { + private static final long serialVersionUID = 0x2F49329E49173487L; + + public InvalidRARFilenameException(String message) { + super(message); + } + } + + public static class RAREntrySegment { + public File file; + public RandomAccessFile raf; + public long headerPos; + public long headerLength; + public long dataPos; + public long dataLength; + public RAREntrySegment nextSegment = null; + + public RAREntrySegment(File file, RandomAccessFile raf, long headerPos, long headerLength, long dataPos, long dataLength/*, RAREntrySegment nextSegment*/) { + this.file = file; + this.raf = raf; + this.headerPos = headerPos; + this.headerLength = headerLength; + this.dataPos = dataPos; + this.dataLength = dataLength; + //this.nextSegment = nextSegment; + } + } + + public class Interval { + public RAREntrySegment segment; + public long startOffset; + public long endOffset; + + public Interval(RAREntrySegment segment, long startOffset, long endOffset) { + this.segment = segment; + this.startOffset = startOffset; + this.endOffset = endOffset; + } + } + + public class IntervalStructure { + private LinkedList intervals = new LinkedList(); + + public void addSegment(RAREntrySegment res, long startOffset, long endOffset) { + Interval newInterval = new Interval(res, startOffset, endOffset); + if(intervals.size() > 0) { + Interval previous = intervals.getLast(); + previous.segment.nextSegment = newInterval.segment; + } + intervals.addLast(newInterval); + } + + public RAREntrySegment getSegment(long offset) { + for(Interval i : intervals) + if(offset >= i.startOffset && offset < i.endOffset) + return i.segment; + return null; + } + + public Interval getInterval(long offset) { + for(Interval i : intervals) + if(offset >= i.startOffset && offset < i.endOffset) + return i; + return null; + } + + public RAREntrySegment[] getAllSegmentsUnordered() { + RAREntrySegment[] result = new RAREntrySegment[intervals.size()]; + int j = 0; + for(Interval i : intervals) + result[j++] = i.segment; + return result; + } + } + + public static boolean debug = false; + private IntervalStructure intervalStructure; + private RAREntrySegment currentSegment; + private boolean closed = false; + private long seekPointer; + private long globalPointer = 0; + private long segmentPointer = 0; + private final long length; + + public RARFileEntryStream(RARFileEntry entry) throws FileNotFoundException, IOException { + this(entry.getFile(0), entry.getOffset(0)); + } + + public RARFileEntryStream(File startFile, long pos) throws FileNotFoundException, IOException { + //this.startFile = startFile; + //this.pos = pos; + + LinkedList entrySegments = getSegments(startFile, pos, new LinkedList(), false); +// for(RAREntrySegment res : entrySegments) +// res.raf.seek(0); + if(entrySegments == null || entrySegments.size() < 1) + throw new FileNotFoundException(); + this.currentSegment = entrySegments.getFirst(); + + long currentPos = 0; + this.intervalStructure = new IntervalStructure(); + for(RAREntrySegment res : entrySegments) { + if(debug) System.out.println("Adding \"" + res.file + "\" at " + currentPos); + intervalStructure.addSegment(res, currentPos, currentPos+res.dataLength); + currentPos += res.dataLength; + } + length = currentPos; + if(debug) System.out.println("Length of entry: " + length + " bytes"); + + seekPointer = -2; + updateSeekPosition(); + } + + /** @see java.io.RandomAccessFile */ + public synchronized void close() throws IOException { + if(debug) System.out.println("RARFileEntryStream.close()"); + if(closed) throw new IOException("Stream closed."); + + closed = true; + for(RAREntrySegment res : intervalStructure.getAllSegmentsUnordered()) + res.raf.close(); + } + + /** @see java.io.RandomAccessFile */ + public synchronized long getFilePointer() throws IOException { + return ((seekPointer == -1)?globalPointer:seekPointer); + } + + /** @see java.io.RandomAccessFile */ + public synchronized long length() throws IOException { + return length; + } + + /** @see java.io.RandomAccessFile */ + public synchronized int read() throws IOException { + if(closed) throw new IOException("Stream closed."); + byte[] b = new byte[1]; + read(b, 0, 1); + return b[0] & 0xFF; + } + + /** @see java.io.RandomAccessFile */ + public synchronized int read(byte[] b) throws IOException { + if(closed) throw new IOException("Stream closed."); + return read(b, 0, b.length); + } + + /** @see java.io.RandomAccessFile */ + public synchronized int read(byte[] b, int off, int len) throws IOException { +// boolean debug = true; + if(debug) System.out.print("RARFileEntryStream.read(b, " + off + ", " + len + ") = "); +// System.out.println(); +// System.out.println("(1)currentSegment.raf.getFilePointer()=" + currentSegment.raf.getFilePointer()); +// System.out.println("(1)=seekPointer=" + seekPointer); + if(closed) throw new IOException("Stream closed."); + try { + updateSeekPosition(); + } catch(IOException ioe) { + if(debug) System.out.println("-1 (Couldn't update seek position)"); + return -1; + } + + long bytesRemainingInSegment = currentSegment.dataLength - segmentPointer; + while(bytesRemainingInSegment == 0) { + if(nextSegment()) + bytesRemainingInSegment = currentSegment.dataLength - segmentPointer; + else { + if(debug) System.out.println("-1 (Nothing to read)"); + return -1; // Nothing to read! + } + } + int bytesRead = 0; + mainLoop: + while(bytesRead < len) { + int remainingBytes = len-bytesRead; + int bytesToRead = (remainingBytes < bytesRemainingInSegment)?remainingBytes:(int)bytesRemainingInSegment; + +// System.out.println("(2)currentSegment.raf.getFilePointer()=" + currentSegment.raf.getFilePointer()); + int currentBytesRead = currentSegment.raf.read(b, off+bytesRead, bytesToRead); + if(currentBytesRead == -1) + throw new IOException("Reached end of file unexpectedly for file \"" + currentSegment.file + "\"! (segmentPointer=" + segmentPointer + " globalPointer=" + globalPointer + " bytesRemainingInSegment=" + bytesRemainingInSegment + " bytesRead=" + bytesRead + " bytesToRead=" + bytesToRead + " currentSegment.raf.getFilePointer()=" + currentSegment.raf.getFilePointer() + " currentSegment.dataLength=" + currentSegment.dataLength + ")"); + + bytesRead += currentBytesRead; + segmentPointer += currentBytesRead; + globalPointer += currentBytesRead; + //seekPointer += currentBytesRead; + + bytesRemainingInSegment = currentSegment.dataLength - segmentPointer; + while(bytesRemainingInSegment == 0) { + if(nextSegment()) + bytesRemainingInSegment = currentSegment.dataLength - segmentPointer; + else + break mainLoop; // No more bytes to read; + } + } + if(debug) System.out.println(bytesRead); + return bytesRead; + } + + /** @see java.io.RandomAccessFile */ + public synchronized void seek(long pos) throws IOException { + if(debug) System.out.println("RARFileEntryStream.seek(" + pos + ")"); + if(closed) throw new IOException("Stream closed."); + if(pos < 0) throw new IOException("Invalid position."); + seekPointer = pos; + } + + private synchronized void updateSeekPosition() throws IOException { + if(debug) System.out.println("updateSeekPosition()"); +// System.out.println("seekPointer=" + seekPointer); +// System.out.println("globalPointer=" + globalPointer); +// System.out.println("segmentPointer=" + segmentPointer); + if(seekPointer == globalPointer) + seekPointer = -1; + else if(seekPointer != -1) { + //if(seekPointer > length) seek(length-1); + if(seekPointer == -2) + seekPointer = 0; + + long posDiff = seekPointer-globalPointer; + long newSegmentPointer = segmentPointer + posDiff; + if(newSegmentPointer >= 0 && + newSegmentPointer < currentSegment.dataLength) { + if(debug) System.out.println(" Pointer is within segment."); + globalPointer += posDiff; + segmentPointer += posDiff; + if(debug) System.out.println(" Seeking to " + (currentSegment.dataPos + segmentPointer) +"."); + currentSegment.raf.seek(currentSegment.dataPos + segmentPointer); + if(debug) System.out.println(" Done."); + } + else { + if(debug) System.out.println(" Pointer is OUTSIDE segment. We need to get a new one."); + Interval newInterval = intervalStructure.getInterval(seekPointer); + if(newInterval == null) + throw new IOException("No more data to read."); + RAREntrySegment newSegment = newInterval.segment; + newSegmentPointer = (seekPointer-newInterval.startOffset); + if(debug) System.out.println(" Seeking to " + (currentSegment.dataPos + segmentPointer) +"."); + newSegment.raf.seek(newSegment.dataPos + newSegmentPointer); + if(debug) System.out.println(" Done."); + + // If we got here, all is well. + globalPointer = seekPointer; + segmentPointer = newSegmentPointer; + currentSegment = newSegment; + } + seekPointer = -1; + } + } + + public synchronized boolean isClosed() { + return closed; + } + + public synchronized boolean reopen() { + if(debug) System.out.println("RARFileEntryStream.reopen()"); + if(closed) { + try { + for(RAREntrySegment res : intervalStructure.getAllSegmentsUnordered()) + res.raf = new RandomAccessFile(res.file, "r"); + closed = false; + seekPointer = -2; + updateSeekPosition(); + //seek(0); + return true; + } catch(Exception e) {} + } + return false; + } + + /** + * Changes forward to the next segment and sets the pointers. + */ + private boolean nextSegment() throws IOException { + if(debug) System.out.println("RARFileEntryStream.nextSegment()"); + RAREntrySegment nextSegment = currentSegment.nextSegment; + if(nextSegment != null) { + if(debug) System.out.println(" nextSegment != null"); + if(debug) System.out.println(" currentSegment: " + currentSegment); + if(debug) System.out.println(" file: " + currentSegment.file); + if(debug) System.out.println(" headerPos: " + currentSegment.headerPos); + if(debug) System.out.println(" headerLength: " + currentSegment.headerLength); + if(debug) System.out.println(" dataPos: " + currentSegment.dataPos); + if(debug) System.out.println(" dataLength: " + currentSegment.dataLength); + if(debug) System.out.println(" nextSegment: " + nextSegment); + if(debug) System.out.println(" file: " + nextSegment.file); + if(debug) System.out.println(" headerPos: " + nextSegment.headerPos); + if(debug) System.out.println(" headerLength: " + nextSegment.headerLength); + if(debug) System.out.println(" dataPos: " + nextSegment.dataPos); + if(debug) System.out.println(" dataLength: " + nextSegment.dataLength); + globalPointer += currentSegment.dataLength - segmentPointer; + segmentPointer = 0; + nextSegment.raf.seek(nextSegment.dataPos); + currentSegment = nextSegment; + return true; + } + else { + if(debug) System.out.println(" nextSegment == null"); + return false; + } + } + + private static LinkedList getSegments(File file, long position, LinkedList list, boolean needsIncoming) throws IOException, FileNotFoundException { + if(debug) System.out.println("Getting new entry segment: " + file); + RandomAccessFile raf = new RandomAccessFile(file, "r"); + raf.seek(position); + RARHeader rh = RARFile.readHeader(raf); + if(rh instanceof NewFileHeader) { + NewFileHeader nfh = (NewFileHeader)rh; + if(//nfh.getMethod() == RARHeader.METHOD_UNCOMPRESSED && + (needsIncoming?nfh.hasIncomingData():!nfh.hasIncomingData()) && + !(nfh instanceof CommentHeader)) { + RAREntrySegment res = new RAREntrySegment(file, raf, position, nfh.getSize(), position+nfh.getSize(), nfh.getPackSize()); + list.addLast(res); + if(nfh.hasOutgoingData()) { + File nextFile = getNextFile(file); + if(nextFile == null) + throw new FileNotFoundException(); + long posInNextFile = getMatchingPosInFile(nextFile, nfh); + if(posInNextFile == -1) + throw new FileNotFoundException(); + getSegments(nextFile, posInNextFile, list, true); + } + } + } + return list; + } + + protected static File getNextFile(File rarFile) { + String filename = rarFile.getName(); + String newFilename; + if(Pattern.matches(".+\\.[P|p]art\\d+\\.rar", filename)) { + String numberString = ""; + long partNumber = 0; // I'm not serious... (: + int charstep = 0; + while(true) { + String currentNumber = filename.substring(filename.length()-5-charstep, filename.length()-4-charstep); + try { + partNumber += Integer.parseInt(currentNumber)*Util.pow(10, charstep); + } catch(NumberFormatException nfe) { + break; + } + ++charstep; + } + int numberLength = charstep; + String newPartNumberString = "" + (partNumber + 1); + + // Add leading zeroes + while(newPartNumberString.length() < numberLength) + newPartNumberString = "0" + newPartNumberString; + + newFilename = filename.substring(0, filename.length()-4-numberLength) + newPartNumberString + ".rar"; + } + else if(Pattern.matches(".+\\.r((ar)|(\\d\\d)){1}", filename)) { + String lastTwo = filename.substring(filename.length()-2, filename.length()); + String newLastTwo = ""; + if(lastTwo.equals("ar")) + newLastTwo = "00"; + else { + try { + int archiveNumber = Integer.parseInt(lastTwo); + newLastTwo = "" + (archiveNumber+1); + if(newLastTwo.length() < 2) + newLastTwo = "0" + newLastTwo; + } catch(NumberFormatException nfe) { + throw new RuntimeException(nfe); + } + } + if(newLastTwo.length() != 2) + throw new InvalidRARFilenameException("Incremented filename invalid."); + + newFilename = filename.substring(0, filename.length()-2) + newLastTwo; + } + else if(Pattern.matches(".+\\.\\d\\d\\d", filename)) { + String lastThree = filename.substring(filename.length()-3, filename.length()); + String newLastThree = ""; + try { + int archiveNumber = Integer.parseInt(lastThree); + newLastThree = "" + (archiveNumber+1); + while(newLastThree.length() < 3) + newLastThree = "0" + newLastThree; + } catch(NumberFormatException nfe) { + throw new RuntimeException(nfe); + } + if(newLastThree.length() != 3) + throw new InvalidRARFilenameException("Incremented filename invalid."); + + newFilename = filename.substring(0, filename.length()-3) + newLastThree; + } + else + throw new InvalidRARFilenameException("Filename in argument invalid for a RAR-file."); + + File newFile = new File(rarFile.getParent(), newFilename); + if(newFile.exists()) + return newFile; + else { + if(debug) System.out.println("Couldn't find \"" + newFile + "\""); + return null; + } + } + + private static long getMatchingPosInFile(File f, NewFileHeader currentHeader) { + try { + RandomAccessFile raf = new RandomAccessFile(f, "r"); + RARFile.readMarkHeader(raf); + RARHeader rh = RARFile.readHeader(raf); + while(rh != null) { + if(rh instanceof NewFileHeader) { + NewFileHeader nextHeader = (NewFileHeader)rh; + if(nextHeader.getFilenameAsString().equals(currentHeader.getFilenameAsString()) && + nextHeader.getUnpSize() == currentHeader.getUnpSize()) { + long pos = raf.getFilePointer()-nextHeader.getPackSize()-nextHeader.getHeadSize(); + raf.close(); + return pos; + } + } + rh = RARFile.readHeader(raf); + } + raf.close(); + } catch(IOException ioe) { + ioe.printStackTrace(); + } + return -1; + } + + /** Testcode */ + public static void main(String[] args) throws IOException { + if(args.length != 3) + System.out.println("RARFileEntryStream self test needs exactly 3 arguments..."); + String rarFilename = args[0]; + String entryName = args[1]; + String referenceFilename = args[2]; + + System.out.println("Initiating self test of RARFileEntryStream..."); + System.out.println(" RAR file: " + rarFilename); + System.out.println(" Entry name: " + entryName); + System.out.println(" Reference file: " + referenceFilename); + + File rarFile = new File(rarFilename); + File referenceFile = new File(referenceFilename); + + long position = RARFile.findEntry(entryName, new RandomAccessFile(rarFile, "r")); + RARFileEntryStream stream = new RARFileEntryStream(rarFile, position); + RandomAccessFile ref = new RandomAccessFile(referenceFile, "r"); + byte[] buffer1 = new byte[4096]; + byte[] buffer2 = new byte[4096]; + int bytesRead1; + int bytesRead2; + long totalBytesRead1; + long totalBytesRead2; + + System.out.println("1. Deterministic tests..."); + if(stream.length() != ref.length()) + System.out.println("Lengths not equal! stream: " + stream.length() + " reference file: " + ref.length()); + + // Testing reading from the beginning + ref.seek(0); + stream.seek(0); + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + bytesRead1 = ref.read(buffer1); + bytesRead2 = stream.read(buffer2); + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + if(bytesRead1 != bytesRead2) throw new RuntimeException("Did not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")"); + if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!"); + + // Going to the middle of the file + ref.seek(ref.length()/2); + stream.seek(stream.length()/2); + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + bytesRead1 = ref.read(buffer1); + bytesRead2 = stream.read(buffer2); + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + if(bytesRead1 != bytesRead2) throw new RuntimeException("Did not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")"); + if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!"); + + // Going to the end of the file + ref.seek(ref.length()); + stream.seek(stream.length()); + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + bytesRead1 = ref.read(buffer1); + bytesRead2 = stream.read(buffer2); + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + if(bytesRead1 != bytesRead2) throw new RuntimeException("Did not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")"); + if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!"); + + // Going beyond the end of the file + ref.seek(ref.length()*2); + stream.seek(stream.length()*2); + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + bytesRead1 = ref.read(buffer1); + bytesRead2 = stream.read(buffer2); + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + if(bytesRead1 != bytesRead2) throw new RuntimeException("Did not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")"); + if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!"); + + // Comparing files byte for byte + System.out.println("Comparing file data..."); + ref.seek(0); + stream.seek(0); + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + totalBytesRead1 = 0; + totalBytesRead2 = 0; + while(totalBytesRead1 < ref.length() && totalBytesRead2 < stream.length()) { + System.out.print("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); + System.out.print(" " + totalBytesRead1 + " bytes processed"); + long bytesRemaining1 = ref.length()-totalBytesRead1; + long bytesRemaining2 = ref.length()-totalBytesRead2; + bytesRead1 = ref.read(buffer1, 0, (bytesRemaining1 < buffer1.length?(int)bytesRemaining1:buffer1.length)); + bytesRead2 = stream.read(buffer2, 0, (bytesRemaining2 < buffer2.length?(int)bytesRemaining2:buffer2.length)); + if(bytesRead1 != bytesRead2) throw new RuntimeException("Have not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")"); + if(bytesRead1 == -1) + break; + totalBytesRead1 += bytesRead1; + totalBytesRead2 += bytesRead2; + if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer()); + if(totalBytesRead1 != totalBytesRead2) throw new RuntimeException("Have not read equal amount of bytes. (totalBytesRead1="+totalBytesRead1+" totalBytesRead2="+totalBytesRead2+")"); + if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!"); + } + System.out.print("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); + System.out.print(" " + totalBytesRead1 + " bytes processed"); + } +} -- cgit v1.2.3