/* * Copyright 2010 Srikanth Reddy Lingala * * 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 net.lingala.zip4j.io; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import net.lingala.zip4j.exception.ZipException; import net.lingala.zip4j.util.InternalZipConstants; import net.lingala.zip4j.util.Raw; import net.lingala.zip4j.util.Zip4jUtil; public class SplitOutputStream extends OutputStream { private RandomAccessFile raf; private long splitLength; private File zipFile; private File outFile; private int currSplitFileCounter; private long bytesWrittenForThisPart; public SplitOutputStream(String name) throws FileNotFoundException, ZipException { this(Zip4jUtil.isStringNotNullAndNotEmpty(name) ? new File(name) : null); } public SplitOutputStream(File file) throws FileNotFoundException, ZipException { this(file, -1); } public SplitOutputStream(String name, long splitLength) throws FileNotFoundException, ZipException { this(!Zip4jUtil.isStringNotNullAndNotEmpty(name) ? new File(name) : null, splitLength); } public SplitOutputStream(File file, long splitLength) throws FileNotFoundException, ZipException { if (splitLength >= 0 && splitLength < InternalZipConstants.MIN_SPLIT_LENGTH) { throw new ZipException("split length less than minimum allowed split length of " + InternalZipConstants.MIN_SPLIT_LENGTH +" Bytes"); } this.raf = new RandomAccessFile(file, InternalZipConstants.WRITE_MODE); this.splitLength = splitLength; this.outFile = file; this.zipFile = file; this.currSplitFileCounter = 0; this.bytesWrittenForThisPart = 0; } public void write(int b) throws IOException { byte[] buff = new byte[1]; buff[0] = (byte) b; write(buff, 0, 1); } public void write(byte[] b) throws IOException { write(b, 0, b.length); } public void write(byte[] b, int off, int len) throws IOException { if (len <= 0) return; if (splitLength != -1) { if (splitLength < InternalZipConstants.MIN_SPLIT_LENGTH) { throw new IOException("split length less than minimum allowed split length of " + InternalZipConstants.MIN_SPLIT_LENGTH +" Bytes"); } if (bytesWrittenForThisPart >= splitLength) { startNextSplitFile(); raf.write(b, off, len); bytesWrittenForThisPart = len; } else if (bytesWrittenForThisPart + len > splitLength) { if (isHeaderData(b)) { startNextSplitFile(); raf.write(b, off, len); bytesWrittenForThisPart = len; } else { raf.write(b, off, (int)(splitLength - bytesWrittenForThisPart)); startNextSplitFile(); raf.write(b, off + (int)(splitLength - bytesWrittenForThisPart), (int)(len - (splitLength - bytesWrittenForThisPart))); bytesWrittenForThisPart = len - (splitLength - bytesWrittenForThisPart); } } else { raf.write(b, off, len); bytesWrittenForThisPart += len; } } else { raf.write(b, off, len); bytesWrittenForThisPart += len; } } private void startNextSplitFile() throws IOException { try { String zipFileWithoutExt = Zip4jUtil.getZipFileNameWithoutExt(outFile.getName()); File currSplitFile = null; String zipFileName = zipFile.getAbsolutePath(); String parentPath = (outFile.getParent() == null)?"":outFile.getParent() + System.getProperty("file.separator"); if (currSplitFileCounter < 9) { currSplitFile = new File(parentPath + zipFileWithoutExt + ".z0" + (currSplitFileCounter + 1)); } else { currSplitFile = new File(parentPath + zipFileWithoutExt + ".z" + (currSplitFileCounter + 1)); } raf.close(); if (currSplitFile.exists()) { throw new IOException("split file: " + currSplitFile.getName() + " already exists in the current directory, cannot rename this file"); } if (!zipFile.renameTo(currSplitFile)) { throw new IOException("cannot rename newly created split file"); } zipFile = new File(zipFileName); raf = new RandomAccessFile(zipFile, InternalZipConstants.WRITE_MODE); currSplitFileCounter++; } catch (ZipException e) { throw new IOException(e.getMessage()); } } private boolean isHeaderData(byte[] buff) { if (buff == null || buff.length < 4) { return false; } int signature = Raw.readIntLittleEndian(buff, 0); long[] allHeaderSignatures = Zip4jUtil.getAllHeaderSignatures(); if (allHeaderSignatures != null && allHeaderSignatures.length > 0) { for (int i = 0; i < allHeaderSignatures.length; i++) { //Ignore split signature if (allHeaderSignatures[i] != InternalZipConstants.SPLITSIG && allHeaderSignatures[i] == signature) { return true; } } } return false; } /** * Checks if the buffer size is sufficient for the current split file. If not * a new split file will be started. * @param bufferSize * @return true if a new split file was started else false * @throws ZipException */ public boolean checkBuffSizeAndStartNextSplitFile(int bufferSize) throws ZipException { if (bufferSize < 0) { throw new ZipException("negative buffersize for checkBuffSizeAndStartNextSplitFile"); } if (!isBuffSizeFitForCurrSplitFile(bufferSize)) { try { startNextSplitFile(); bytesWrittenForThisPart = 0; return true; } catch (IOException e) { throw new ZipException(e); } } return false; } /** * Checks if the given buffer size will be fit in the current split file. * If this output stream is a non-split file, then this method always returns true * @param bufferSize * @return true if the buffer size is fit in the current split file or else false. * @throws ZipException */ public boolean isBuffSizeFitForCurrSplitFile(int bufferSize) throws ZipException { if (bufferSize < 0) { throw new ZipException("negative buffersize for isBuffSizeFitForCurrSplitFile"); } if (splitLength >= InternalZipConstants.MIN_SPLIT_LENGTH) { return (bytesWrittenForThisPart + bufferSize <= splitLength); } else { //Non split zip -- return true return true; } } public void seek(long pos) throws IOException { raf.seek(pos); } public void close() throws IOException { if (raf != null) raf.close(); } public void flush() throws IOException { } public long getFilePointer() throws IOException { return raf.getFilePointer(); } public boolean isSplitZipFile() { return splitLength!=-1; } public long getSplitLength() { return splitLength; } public int getCurrSplitFileCounter() { return currSplitFileCounter; } }