]> WPIA git - gigi.git/blobdiff - lib/jtar/org/kamranzafar/jtar/TarInputStream.java
Adding jtar
[gigi.git] / lib / jtar / org / kamranzafar / jtar / TarInputStream.java
diff --git a/lib/jtar/org/kamranzafar/jtar/TarInputStream.java b/lib/jtar/org/kamranzafar/jtar/TarInputStream.java
new file mode 100644 (file)
index 0000000..cd48ae0
--- /dev/null
@@ -0,0 +1,249 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0 
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class TarInputStream extends FilterInputStream {
+
+       private static final int SKIP_BUFFER_SIZE = 2048;
+       private TarEntry currentEntry;
+       private long currentFileSize;
+       private long bytesRead;
+       private boolean defaultSkip = false;
+
+       public TarInputStream(InputStream in) {
+               super(in);
+               currentFileSize = 0;
+               bytesRead = 0;
+       }
+
+       @Override
+       public boolean markSupported() {
+               return false;
+       }
+
+       /**
+        * Not supported
+        * 
+        */
+       @Override
+       public synchronized void mark(int readlimit) {
+       }
+
+       /**
+        * Not supported
+        * 
+        */
+       @Override
+       public synchronized void reset() throws IOException {
+               throw new IOException("mark/reset not supported");
+       }
+
+       /**
+        * Read a byte
+        * 
+        * @see java.io.FilterInputStream#read()
+        */
+       @Override
+       public int read() throws IOException {
+               byte[] buf = new byte[1];
+
+               int res = this.read(buf, 0, 1);
+
+               if (res != -1) {
+                       return 0xFF & buf[0];
+               }
+
+               return res;
+       }
+
+       /**
+        * Checks if the bytes being read exceed the entry size and adjusts the byte
+        * array length. Updates the byte counters
+        * 
+        * 
+        * @see java.io.FilterInputStream#read(byte[], int, int)
+        */
+       @Override
+       public int read(byte[] b, int off, int len) throws IOException {
+               if (currentEntry != null) {
+                       if (currentFileSize == currentEntry.getSize()) {
+                               return -1;
+                       } else if ((currentEntry.getSize() - currentFileSize) < len) {
+                               len = (int) (currentEntry.getSize() - currentFileSize);
+                       }
+               }
+
+               int br = super.read(b, off, len);
+
+               if (br != -1) {
+                       if (currentEntry != null) {
+                               currentFileSize += br;
+                       }
+
+                       bytesRead += br;
+               }
+
+               return br;
+       }
+
+       /**
+        * Returns the next entry in the tar file
+        * 
+        * @return TarEntry
+        * @throws IOException
+        */
+       public TarEntry getNextEntry() throws IOException {
+               closeCurrentEntry();
+
+               byte[] header = new byte[TarConstants.HEADER_BLOCK];
+               byte[] theader = new byte[TarConstants.HEADER_BLOCK];
+               int tr = 0;
+
+               // Read full header
+               while (tr < TarConstants.HEADER_BLOCK) {
+                       int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
+
+                       if (res < 0) {
+                               break;
+                       }
+
+                       System.arraycopy(theader, 0, header, tr, res);
+                       tr += res;
+               }
+
+               // Check if record is null
+               boolean eof = true;
+               for (byte b : header) {
+                       if (b != 0) {
+                               eof = false;
+                               break;
+                       }
+               }
+
+               if (!eof) {
+                       currentEntry = new TarEntry(header);
+               }
+
+               return currentEntry;
+       }
+
+       /**
+        * Returns the current offset (in bytes) from the beginning of the stream. 
+        * This can be used to find out at which point in a tar file an entry's content begins, for instance. 
+        */
+       public long getCurrentOffset() {
+               return bytesRead;
+       }
+       
+       /**
+        * Closes the current tar entry
+        * 
+        * @throws IOException
+        */
+       protected void closeCurrentEntry() throws IOException {
+               if (currentEntry != null) {
+                       if (currentEntry.getSize() > currentFileSize) {
+                               // Not fully read, skip rest of the bytes
+                               long bs = 0;
+                               while (bs < currentEntry.getSize() - currentFileSize) {
+                                       long res = skip(currentEntry.getSize() - currentFileSize - bs);
+
+                                       if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
+                                               // I suspect file corruption
+                                               throw new IOException("Possible tar file corruption");
+                                       }
+
+                                       bs += res;
+                               }
+                       }
+
+                       currentEntry = null;
+                       currentFileSize = 0L;
+                       skipPad();
+               }
+       }
+
+       /**
+        * Skips the pad at the end of each tar entry file content
+        * 
+        * @throws IOException
+        */
+       protected void skipPad() throws IOException {
+               if (bytesRead > 0) {
+                       int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
+
+                       if (extra > 0) {
+                               long bs = 0;
+                               while (bs < TarConstants.DATA_BLOCK - extra) {
+                                       long res = skip(TarConstants.DATA_BLOCK - extra - bs);
+                                       bs += res;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Skips 'n' bytes on the InputStream<br>
+        * Overrides default implementation of skip
+        * 
+        */
+       @Override
+       public long skip(long n) throws IOException {
+               if (defaultSkip) {
+                       // use skip method of parent stream
+                       // may not work if skip not implemented by parent
+                       long bs = super.skip(n);
+                       bytesRead += bs;
+
+                       return bs;
+               }
+
+               if (n <= 0) {
+                       return 0;
+               }
+
+               long left = n;
+               byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
+
+               while (left > 0) {
+                       int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
+                       if (res < 0) {
+                               break;
+                       }
+                       left -= res;
+               }
+
+               return n - left;
+       }
+
+       public boolean isDefaultSkip() {
+               return defaultSkip;
+       }
+
+       public void setDefaultSkip(boolean defaultSkip) {
+               this.defaultSkip = defaultSkip;
+       }
+}