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.util;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.io.RandomAccessFile;
  import java.io.UnsupportedEncodingException;
  import java.util.ArrayList;
  import java.util.HashMap;
  
  import net.lingala.zip4j.core.HeaderReader;
  import net.lingala.zip4j.core.HeaderWriter;
  import net.lingala.zip4j.exception.ZipException;
  import net.lingala.zip4j.io.SplitOutputStream;
  import net.lingala.zip4j.model.FileHeader;
  import net.lingala.zip4j.model.LocalFileHeader;
  import net.lingala.zip4j.model.Zip64EndCentralDirLocator;
  import net.lingala.zip4j.model.Zip64EndCentralDirRecord;
  import net.lingala.zip4j.model.ZipModel;
  import net.lingala.zip4j.progress.ProgressMonitor;
  
  public class ArchiveMaintainer {
  	
  	public ArchiveMaintainer() {
  	}
  	
  	public HashMap removeZipFile(final ZipModel zipModel, 
  			final FileHeader fileHeader, final ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
  		
  		if (runInThread) {
  			Thread thread = new Thread(InternalZipConstants.THREAD_NAME) {
  				public void run() {
  					try {
  						initRemoveZipFile(zipModel, fileHeader, progressMonitor);
  						progressMonitor.endProgressMonitorSuccess();
  					} catch (ZipException e) {
  					}
  				}
  			};
  			thread.start();
  			return null;
  		} else {
  			HashMap retMap = initRemoveZipFile(zipModel, fileHeader, progressMonitor);
  			progressMonitor.endProgressMonitorSuccess();
  			return retMap;
  		}
  		
  	}
  	
  	public HashMap initRemoveZipFile(ZipModel zipModel, 
  			FileHeader fileHeader, ProgressMonitor progressMonitor) throws ZipException {
  		
  		if (fileHeader == null || zipModel == null) {
  			throw new ZipException("input parameters is null in maintain zip file, cannot remove file from archive");
  		}
  		
  		OutputStream outputStream = null;
  		File zipFile = null;
  		RandomAccessFile inputStream = null;
  		boolean successFlag = false;
  		String tmpZipFileName = null;
  		HashMap retMap = new HashMap();
  		
  		try {
  			int indexOfFileHeader = Zip4jUtil.getIndexOfFileHeader(zipModel, fileHeader);
  			
  			if (indexOfFileHeader < 0) {
  				throw new ZipException("file header not found in zip model, cannot remove file");
  			}
  			
  			if (zipModel.isSplitArchive()) {
  				throw new ZipException("This is a split archive. Zip file format does not allow updating split/spanned files");
  			}
  			
  			long currTime = System.currentTimeMillis();
  			tmpZipFileName = zipModel.getZipFile() + currTime%1000;
  			File tmpFile = new File(tmpZipFileName);
  			
  			while (tmpFile.exists()) {
  				currTime = System.currentTimeMillis();
  				tmpZipFileName = zipModel.getZipFile() + currTime%1000;
  				tmpFile = new File(tmpZipFileName);
  			}
  			
  			try {
  				outputStream = new SplitOutputStream(new File(tmpZipFileName));
  			} catch (FileNotFoundException e1) {
  				throw new ZipException(e1);
  			}
  			
  			zipFile = new File(zipModel.getZipFile());
  			
  			inputStream = createFileHandler(zipModel, InternalZipConstants.READ_MODE);
  			
  			HeaderReader headerReader = new HeaderReader(inputStream);
  			LocalFileHeader localFileHeader = headerReader.readLocalFileHeader(fileHeader);
  			if (localFileHeader == null) {
  				throw new ZipException("invalid local file header, cannot remove file from archive");
  			}
  			
  			long offsetLocalFileHeader = fileHeader.getOffsetLocalHeader();
  			
  			if (fileHeader.getZip64ExtendedInfo() != null && 
  					fileHeader.getZip64ExtendedInfo().getOffsetLocalHeader() != -1) {
  				offsetLocalFileHeader = fileHeader.getZip64ExtendedInfo().getOffsetLocalHeader();
  			}
  			
  			long offsetEndOfCompressedFile = -1;
  			
  			long offsetStartCentralDir = zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir();
  			if (zipModel.isZip64Format()) {
  				if (zipModel.getZip64EndCentralDirRecord() != null) {
  					offsetStartCentralDir = zipModel.getZip64EndCentralDirRecord().getOffsetStartCenDirWRTStartDiskNo();
  				}
  			}
  			
  			ArrayList fileHeaderList = zipModel.getCentralDirectory().getFileHeaders();
  			
  			if (indexOfFileHeader == fileHeaderList.size() - 1) {
  				offsetEndOfCompressedFile = offsetStartCentralDir - 1;
  			} else {
  				FileHeader nextFileHeader = (FileHeader)fileHeaderList.get(indexOfFileHeader + 1);
  				if (nextFileHeader != null) {
  					offsetEndOfCompressedFile = nextFileHeader.getOffsetLocalHeader() - 1;
  					if (nextFileHeader.getZip64ExtendedInfo() != null && 
  							nextFileHeader.getZip64ExtendedInfo().getOffsetLocalHeader() != -1) {
  						offsetEndOfCompressedFile = nextFileHeader.getZip64ExtendedInfo().getOffsetLocalHeader() - 1;
  					}
  				}
  			}
  			
  			if (offsetLocalFileHeader < 0 || offsetEndOfCompressedFile < 0) {
  				throw new ZipException("invalid offset for start and end of local file, cannot remove file");
  			}
  			
  			if(indexOfFileHeader == 0) {
  				if (zipModel.getCentralDirectory().getFileHeaders().size() > 1) {
  					// if this is the only file and it is deleted then no need to do this
  					copyFile(inputStream, outputStream, offsetEndOfCompressedFile + 1, offsetStartCentralDir, progressMonitor);
  				}	
  			} else if (indexOfFileHeader == fileHeaderList.size() - 1) {
  				copyFile(inputStream, outputStream, 0, offsetLocalFileHeader, progressMonitor);
  			} else {
  				copyFile(inputStream, outputStream, 0, offsetLocalFileHeader, progressMonitor);
  				copyFile(inputStream, outputStream, offsetEndOfCompressedFile + 1, offsetStartCentralDir, progressMonitor);
  			}
  			
  			if (progressMonitor.isCancelAllTasks()) {
  				progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
  				progressMonitor.setState(ProgressMonitor.STATE_READY);
  				return null;
  			}
  			
  			zipModel.getEndCentralDirRecord().setOffsetOfStartOfCentralDir(((SplitOutputStream)outputStream).getFilePointer());
  			zipModel.getEndCentralDirRecord().setTotNoOfEntriesInCentralDir(
  					zipModel.getEndCentralDirRecord().getTotNoOfEntriesInCentralDir() - 1);
  			zipModel.getEndCentralDirRecord().setTotNoOfEntriesInCentralDirOnThisDisk(
  					zipModel.getEndCentralDirRecord().getTotNoOfEntriesInCentralDirOnThisDisk() - 1);
  			
  			zipModel.getCentralDirectory().getFileHeaders().remove(indexOfFileHeader);
  			
  			for (int i = indexOfFileHeader; i < zipModel.getCentralDirectory().getFileHeaders().size(); i++) {
  				long offsetLocalHdr = ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getOffsetLocalHeader();
  				if (((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getZip64ExtendedInfo() != null && 
  						((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getZip64ExtendedInfo().getOffsetLocalHeader() != -1) {
  					offsetLocalHdr = ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getZip64ExtendedInfo().getOffsetLocalHeader();
  				}
  				
  				((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).setOffsetLocalHeader(
  						offsetLocalHdr - (offsetEndOfCompressedFile - offsetLocalFileHeader) - 1);
  			}
  			
  			HeaderWriter headerWriter = new HeaderWriter();
  			headerWriter.finalizeZipFile(zipModel, outputStream);
  			
  			successFlag = true;
  			
  			retMap.put(InternalZipConstants.OFFSET_CENTRAL_DIR, 
  					Long.toString(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir()));
  			
  		} catch (ZipException e) {
  			progressMonitor.endProgressMonitorError(e);
  			throw e;
  		} catch (Exception e) {
  			progressMonitor.endProgressMonitorError(e);
  			throw new ZipException(e);
  		} finally {
  			try {
  				if (inputStream != null)
  					inputStream.close();
  				if (outputStream != null)
  					outputStream.close();
  			} catch (IOException e) {
  				throw new ZipException("cannot close input stream or output stream when trying to delete a file from zip file");
  			}
  			
  			if (successFlag) {
  				restoreFileName(zipFile, tmpZipFileName);
  			} else {
  				File newZipFile = new File(tmpZipFileName);
  				newZipFile.delete();				
  			}
  		}
  		
  		return retMap;
  	}
  	
  	private void restoreFileName(File zipFile, String tmpZipFileName) throws ZipException {
  		if (zipFile.delete())
  		{
  			File newZipFile = new File(tmpZipFileName);
  			if (!newZipFile.renameTo(zipFile)) {
  				throw new ZipException("cannot rename modified zip file");
  			}
  		} else {
  			throw new ZipException("cannot delete old zip file");
  		}
  	}
  	
  	private void copyFile(RandomAccessFile inputStream, 
  			OutputStream outputStream, long start, long end, ProgressMonitor progressMonitor) throws ZipException {
  		
  		if (inputStream == null || outputStream == null) {
  			throw new ZipException("input or output stream is null, cannot copy file");
  		}
  		
  		if (start < 0) {
  			throw new ZipException("starting offset is negative, cannot copy file");
  		}
  		
  		if (end < 0) {
  			throw new ZipException("end offset is negative, cannot copy file");
  		}
  		
  		if (start > end) {
  			throw new ZipException("start offset is greater than end offset, cannot copy file");
  		}
  		
  		if (start == end) {
  			return;
  		}
  		
  		if (progressMonitor.isCancelAllTasks()) {
  			progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
  			progressMonitor.setState(ProgressMonitor.STATE_READY);
  			return;
  		}
  		
  		try {
  			inputStream.seek(start);
  			
  			int readLen = -2;
  			byte[] buff;
  			long bytesRead = 0;
  			long bytesToRead = end - start;
  			
  			if ((end - start) < InternalZipConstants.BUFF_SIZE) {
  				buff = new byte[(int)(end - start)];
  			} else {
  				buff = new byte[InternalZipConstants.BUFF_SIZE];
  			}
  			
  			while ((readLen = inputStream.read(buff)) != -1) {
  				outputStream.write(buff, 0, readLen);
  				
  				progressMonitor.updateWorkCompleted(readLen);
  				if (progressMonitor.isCancelAllTasks()) {
  					progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
  					return;
  				}
  				
  				bytesRead += readLen;
  				
  				if(bytesRead == bytesToRead) {
  					break;
  				} else if (bytesRead + buff.length > bytesToRead) {
  					buff = new byte[(int)(bytesToRead - bytesRead)];
  				}
  			}
  			
  		} catch (IOException e) {
  			throw new ZipException(e);
  		} catch (Exception e) {
  			throw new ZipException(e);
  		}
  	}
  	
  	private RandomAccessFile createFileHandler(ZipModel zipModel, String mode) throws ZipException {
  		if (zipModel == null || !Zip4jUtil.isStringNotNullAndNotEmpty(zipModel.getZipFile())) {
  			throw new ZipException("input parameter is null in getFilePointer, cannot create file handler to remove file");
  		}
  		
  		try {
  			return new RandomAccessFile(new File(zipModel.getZipFile()), mode);
  		} catch (FileNotFoundException e) {
  			throw new ZipException(e);
  		}
  	}
  	
  	/**
  	 * Merges split Zip files into a single Zip file
  	 * @param zipModel
  	 * @throws ZipException
  	 */
  	public void mergeSplitZipFiles(final ZipModel zipModel, final File outputZipFile, 
  			final ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
  		if (runInThread) {
  			Thread thread = new Thread(InternalZipConstants.THREAD_NAME) {
  				public void run() {
  					try {
  						initMergeSplitZipFile(zipModel, outputZipFile, progressMonitor);
  					} catch (ZipException e) {
  					}
  				}
  			};
  			thread.start();
  		} else {
  			initMergeSplitZipFile(zipModel, outputZipFile, progressMonitor);
  		}
  	}
  	
  	private void initMergeSplitZipFile(ZipModel zipModel, File outputZipFile, 
  			ProgressMonitor progressMonitor) throws ZipException {
  		if (zipModel == null) {
  			ZipException e = new ZipException("one of the input parameters is null, cannot merge split zip file");
  			progressMonitor.endProgressMonitorError(e);
  			throw e;
  		}
  		
  		if (!zipModel.isSplitArchive()) {
  			ZipException e = new ZipException("archive not a split zip file");
  			progressMonitor.endProgressMonitorError(e);
  			throw e;
  		}
  		
  		OutputStream outputStream = null;
  		RandomAccessFile inputStream = null;
  		ArrayList fileSizeList = new ArrayList();
  		long totBytesWritten = 0;
  		boolean splitSigRemoved = false;
  		try {
  			
  			int totNoOfSplitFiles = zipModel.getEndCentralDirRecord().getNoOfThisDisk();
  			
  			if (totNoOfSplitFiles <= 0) {
  				throw new ZipException("corrupt zip model, archive not a split zip file");
  			}
  			
  			outputStream = prepareOutputStreamForMerge(outputZipFile);
  			for (int i = 0; i <= totNoOfSplitFiles; i++) {
  				inputStream = createSplitZipFileHandler(zipModel, i);
  				
  				int start = 0;
  				Long end = new Long(inputStream.length());
  				
  				if (i == 0) {
  					if (zipModel.getCentralDirectory() != null && 
  							zipModel.getCentralDirectory().getFileHeaders() != null && 
  							zipModel.getCentralDirectory().getFileHeaders().size() > 0) {
  						byte[] buff = new byte[4];
  						inputStream.seek(0);
  						inputStream.read(buff);
  						if (Raw.readIntLittleEndian(buff, 0) == InternalZipConstants.SPLITSIG) {
  							start = 4;
  							splitSigRemoved = true;
  						}
  					}
  				}
  				
  				if (i == totNoOfSplitFiles) {
  					end = new Long(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir());
  				}
  				
  				copyFile(inputStream, outputStream, start, end.longValue(), progressMonitor);
  				totBytesWritten += (end.longValue() - start);
  				if (progressMonitor.isCancelAllTasks()) {
  					progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
  					progressMonitor.setState(ProgressMonitor.STATE_READY);
  					return;
  				}
  				
  				fileSizeList.add(end);
  				
  				try {
  					inputStream.close();
  				} catch (IOException e) {
  					//ignore
  				}
  			}
  			
  			ZipModel newZipModel = (ZipModel)zipModel.clone();
  			newZipModel.getEndCentralDirRecord().setOffsetOfStartOfCentralDir(totBytesWritten);
  			
  			updateSplitZipModel(newZipModel, fileSizeList, splitSigRemoved);
  			
  			HeaderWriter headerWriter = new HeaderWriter();
  			headerWriter.finalizeZipFileWithoutValidations(newZipModel, outputStream);
  			
  			progressMonitor.endProgressMonitorSuccess();
  			
  		} catch (IOException e) {
  			progressMonitor.endProgressMonitorError(e);
  			throw new ZipException(e);
  		} catch (Exception e) {
  			progressMonitor.endProgressMonitorError(e);
  			throw new ZipException(e);
  		} finally {
  			if (outputStream != null) {
  				try {
  					outputStream.close();
  				} catch (IOException e) {
  					//ignore
  				}
  			}
  			
  			if (inputStream != null) {
  				try {
  					inputStream.close();
  				} catch (IOException e) {
  					// ignore
  				}
  			}
  		}
  	}
  	
  	/**
  	 * Creates an input stream for the split part of the zip file
  	 * @return Zip4jInputStream
  	 * @throws ZipException
  	 */
  	
  	private RandomAccessFile createSplitZipFileHandler(ZipModel zipModel, int partNumber) throws ZipException {
  		if (zipModel == null) {
  			throw new ZipException("zip model is null, cannot create split file handler");
  		}
  		
  		if (partNumber < 0) {
  			throw new ZipException("invlaid part number, cannot create split file handler");
  		}
  		
  		try {
  			String curZipFile = zipModel.getZipFile();
  			String partFile = null;
  			if (partNumber == zipModel.getEndCentralDirRecord().getNoOfThisDisk()) {
  				partFile = zipModel.getZipFile();
  			} else {
  				if (partNumber >= 9) {
  					partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z" + (partNumber+ 1);
  				} else{
  					partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z0" + (partNumber+ 1);
  				}
  			}
  			File tmpFile = new File(partFile);
  			
  			if (!Zip4jUtil.checkFileExists(tmpFile)) {
  				throw new ZipException("split file does not exist: " + partFile);
  			}
  			
  			return new RandomAccessFile(tmpFile, InternalZipConstants.READ_MODE);
  		} catch (FileNotFoundException e) {
  			throw new ZipException(e);
  		} catch (Exception e) {
  			throw new ZipException(e);
  		}
  		
  	}
  	
  	private OutputStream prepareOutputStreamForMerge(File outFile) throws ZipException {
  		if (outFile == null) {
  			throw new ZipException("outFile is null, cannot create outputstream");
  		}
  		
  		try {
  			return new FileOutputStream(outFile);
  		} catch (FileNotFoundException e) {
  			throw new ZipException(e);
  		} catch (Exception e) {
  			throw new ZipException(e);
  		}
  	}
  	
  	private void updateSplitZipModel(ZipModel zipModel, ArrayList fileSizeList, boolean splitSigRemoved) throws ZipException {
  		if (zipModel == null) {
  			throw new ZipException("zip model is null, cannot update split zip model");
  		}
  		
  		zipModel.setSplitArchive(false);
  		updateSplitFileHeader(zipModel, fileSizeList, splitSigRemoved);
  		updateSplitEndCentralDirectory(zipModel);
  		if (zipModel.isZip64Format()) {
  			updateSplitZip64EndCentralDirLocator(zipModel, fileSizeList);
  			updateSplitZip64EndCentralDirRec(zipModel, fileSizeList);
  		}
  	}
  	
  	private void updateSplitFileHeader(ZipModel zipModel, ArrayList fileSizeList, boolean splitSigRemoved) throws ZipException {
  		try {
  			
  			if (zipModel.getCentralDirectory()== null) {
  				throw new ZipException("corrupt zip model - getCentralDirectory, cannot update split zip model");
  			}
  			
  			int fileHeaderCount = zipModel.getCentralDirectory().getFileHeaders().size();
  			int splitSigOverhead = 0;
  			if (splitSigRemoved)
  				splitSigOverhead = 4;
  			
  			for (int i = 0; i < fileHeaderCount; i++) {
  				long offsetLHToAdd = 0;
  				
  				for (int j = 0; j < ((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getDiskNumberStart(); j++) {
  					offsetLHToAdd += ((Long)fileSizeList.get(j)).longValue();
  				}
  				((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).setOffsetLocalHeader(
  						((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).getOffsetLocalHeader() +
  						offsetLHToAdd - splitSigOverhead);
  				((FileHeader)zipModel.getCentralDirectory().getFileHeaders().get(i)).setDiskNumberStart(0);
  			}
  			
  		} catch (ZipException e) {
  			throw e;
  		} catch (Exception e) {
  			throw new ZipException(e);
  		}
  	}
  	
  	private void updateSplitEndCentralDirectory(ZipModel zipModel) throws ZipException {
  		try {
  			if (zipModel == null) {
  				throw new ZipException("zip model is null - cannot update end of central directory for split zip model");
  			}
  			
  			if (zipModel.getCentralDirectory()== null) {
  				throw new ZipException("corrupt zip model - getCentralDirectory, cannot update split zip model");
  			}
  			
  			zipModel.getEndCentralDirRecord().setNoOfThisDisk(0);
  			zipModel.getEndCentralDirRecord().setNoOfThisDiskStartOfCentralDir(0);
  			zipModel.getEndCentralDirRecord().setTotNoOfEntriesInCentralDir(
  					zipModel.getCentralDirectory().getFileHeaders().size());
  			zipModel.getEndCentralDirRecord().setTotNoOfEntriesInCentralDirOnThisDisk(
  					zipModel.getCentralDirectory().getFileHeaders().size());
  			
  		} catch (ZipException e) {
  			throw e;
  		} catch (Exception e) {
  			throw new ZipException(e);
  		}
  	}
  	
  	private void updateSplitZip64EndCentralDirLocator(ZipModel zipModel, ArrayList fileSizeList) throws ZipException {
  		if (zipModel == null) {
  			throw new ZipException("zip model is null, cannot update split Zip64 end of central directory locator");
  		}
  		
  		if (zipModel.getZip64EndCentralDirLocator() == null) {
  			return;
  		}
  		
  		zipModel.getZip64EndCentralDirLocator().setNoOfDiskStartOfZip64EndOfCentralDirRec(0);
  		long offsetZip64EndCentralDirRec = 0;
  		
  		for (int i = 0; i < fileSizeList.size(); i++) {
  			offsetZip64EndCentralDirRec += ((Long)fileSizeList.get(i)).longValue();
  		}
  		zipModel.getZip64EndCentralDirLocator().setOffsetZip64EndOfCentralDirRec(
  				((Zip64EndCentralDirLocator)zipModel.getZip64EndCentralDirLocator()).getOffsetZip64EndOfCentralDirRec() + 
  				offsetZip64EndCentralDirRec);
  		zipModel.getZip64EndCentralDirLocator().setTotNumberOfDiscs(1);
  	}
  	
  	private void updateSplitZip64EndCentralDirRec(ZipModel zipModel, ArrayList fileSizeList) throws ZipException {
  		if (zipModel == null) {
  			throw new ZipException("zip model is null, cannot update split Zip64 end of central directory record");
  		}
  		
  		if (zipModel.getZip64EndCentralDirRecord() == null) {
  			return;
  		}
  		
  		zipModel.getZip64EndCentralDirRecord().setNoOfThisDisk(0);
  		zipModel.getZip64EndCentralDirRecord().setNoOfThisDiskStartOfCentralDir(0);
  		zipModel.getZip64EndCentralDirRecord().setTotNoOfEntriesInCentralDirOnThisDisk(
  				zipModel.getEndCentralDirRecord().getTotNoOfEntriesInCentralDir());
  		
  		long offsetStartCenDirWRTStartDiskNo = 0;
  		
  		for (int i = 0; i < fileSizeList.size(); i++) {
  			offsetStartCenDirWRTStartDiskNo += ((Long)fileSizeList.get(i)).longValue();
  		}
  		
  		zipModel.getZip64EndCentralDirRecord().setOffsetStartCenDirWRTStartDiskNo(
  				((Zip64EndCentralDirRecord)zipModel.getZip64EndCentralDirRecord()).getOffsetStartCenDirWRTStartDiskNo() + 
  				offsetStartCenDirWRTStartDiskNo);
  	}
  	
  	public void setComment(ZipModel zipModel, String comment) throws ZipException {
  		if (comment == null) {
  			throw new ZipException("comment is null, cannot update Zip file with comment");
  		}
  		
  		if (zipModel == null) {
  			throw new ZipException("zipModel is null, cannot update Zip file with comment");
  		}
  		
  		String encodedComment = comment;
  		byte[] commentBytes = comment.getBytes();
  		int commentLength = comment.length();
  		
  		if (Zip4jUtil.isSupportedCharset(InternalZipConstants.CHARSET_COMMENTS_DEFAULT)) {
  			try {
  				encodedComment = new String(comment.getBytes(InternalZipConstants.CHARSET_COMMENTS_DEFAULT), InternalZipConstants.CHARSET_COMMENTS_DEFAULT);
  				commentBytes = encodedComment.getBytes(InternalZipConstants.CHARSET_COMMENTS_DEFAULT);
  				commentLength = encodedComment.length();
  			} catch (UnsupportedEncodingException e) {
  				encodedComment = comment;
  				commentBytes = comment.getBytes();
  				commentLength = comment.length();
  			}
  		}
  		
  		if (commentLength > InternalZipConstants.MAX_ALLOWED_ZIP_COMMENT_LENGTH) {
  			throw new ZipException("comment length exceeds maximum length");
  		}
  		
  		zipModel.getEndCentralDirRecord().setComment(encodedComment);
  		zipModel.getEndCentralDirRecord().setCommentBytes(commentBytes);
  		zipModel.getEndCentralDirRecord().setCommentLength(commentLength);
  		
  		SplitOutputStream outputStream = null;
  		
  		try {
  			HeaderWriter headerWriter = new HeaderWriter();
  			outputStream = new SplitOutputStream(zipModel.getZipFile());
  			
  			if (zipModel.isZip64Format()) {
  				outputStream.seek(zipModel.getZip64EndCentralDirRecord().getOffsetStartCenDirWRTStartDiskNo());
  			} else {
  				outputStream.seek(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir());
  			}
  			
  			headerWriter.finalizeZipFileWithoutValidations(zipModel, outputStream);
  		} catch (FileNotFoundException e) {
  			throw new ZipException(e);
  		} catch (IOException e) {
  			throw new ZipException(e);
  		} finally {
  			if (outputStream != null) {
  				try {
  					outputStream.close();
  				} catch (IOException e) {
  					//ignore
  				}
  			}
  		}
  	}
  	
  	public void initProgressMonitorForRemoveOp(ZipModel zipModel, 
  			FileHeader fileHeader, ProgressMonitor progressMonitor) throws ZipException {
  		if (zipModel == null || fileHeader == null || progressMonitor == null) {
  			throw new ZipException("one of the input parameters is null, cannot calculate total work");
  		}
  		
  		progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_REMOVE);
  		progressMonitor.setFileName(fileHeader.getFileName());
  		progressMonitor.setTotalWork(calculateTotalWorkForRemoveOp(zipModel, fileHeader));
  		progressMonitor.setState(ProgressMonitor.STATE_BUSY);
  	}
  	
  	private long calculateTotalWorkForRemoveOp(ZipModel zipModel, FileHeader fileHeader) throws ZipException {
  		return Zip4jUtil.getFileLengh(new File(zipModel.getZipFile())) - fileHeader.getCompressedSize();
  	}
  	
  	public void initProgressMonitorForMergeOp(ZipModel zipModel, ProgressMonitor progressMonitor) throws ZipException {
  		if (zipModel == null) {
  			throw new ZipException("zip model is null, cannot calculate total work for merge op");
  		}
  		
  		progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_MERGE);
  		progressMonitor.setFileName(zipModel.getZipFile());
  		progressMonitor.setTotalWork(calculateTotalWorkForMergeOp(zipModel));
  		progressMonitor.setState(ProgressMonitor.STATE_BUSY);
  	}
  	
  	private long calculateTotalWorkForMergeOp(ZipModel zipModel) throws ZipException {
  		long totSize = 0;
  		if (zipModel.isSplitArchive()) {
  			int totNoOfSplitFiles = zipModel.getEndCentralDirRecord().getNoOfThisDisk();
  			String partFile = null;
  			String curZipFile = zipModel.getZipFile();
  			int partNumber = 0;
  			for (int i = 0; i <= totNoOfSplitFiles; i++) {
  				if (partNumber == zipModel.getEndCentralDirRecord().getNoOfThisDisk()) {
  					partFile = zipModel.getZipFile();
  				} else {
  					if (partNumber >= 9) {
  						partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z" + (partNumber+ 1);
  					} else{
  						partFile = curZipFile.substring(0, curZipFile.lastIndexOf(".")) + ".z0" + (partNumber+ 1);
  					}
  				}
  				
  				totSize += Zip4jUtil.getFileLengh(new File(partFile)); 
  			}
  			
  		}
  		return totSize;
  	}
  }
  