content: /*
  * 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 
  * 
  * https://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;
  	}
  }
  