/*
* 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.zip;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.io.SplitOutputStream;
import net.lingala.zip4j.io.ZipOutputStream;
import net.lingala.zip4j.model.EndCentralDirRecord;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.util.ArchiveMaintainer;
import net.lingala.zip4j.util.CRCUtil;
import net.lingala.zip4j.util.InternalZipConstants;
import net.lingala.zip4j.util.Zip4jConstants;
import net.lingala.zip4j.util.Zip4jUtil;
public class ZipEngine {
private ZipModel zipModel;
public ZipEngine(ZipModel zipModel) throws ZipException {
if (zipModel == null) {
throw new ZipException("zip model is null in ZipEngine constructor");
}
this.zipModel = zipModel;
}
public void addFiles(final ArrayList fileList, final ZipParameters parameters,
final ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
if (fileList == null || parameters == null) {
throw new ZipException("one of the input parameters is null when adding files");
}
if(fileList.size() <= 0) {
throw new ZipException("no files to add");
}
progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_ADD);
progressMonitor.setState(ProgressMonitor.STATE_BUSY);
progressMonitor.setResult(ProgressMonitor.RESULT_WORKING);
if (runInThread) {
progressMonitor.setTotalWork(calculateTotalWork(fileList, parameters));
progressMonitor.setFileName(((File)fileList.get(0)).getAbsolutePath());
Thread thread = new Thread(InternalZipConstants.THREAD_NAME) {
public void run() {
try {
initAddFiles(fileList, parameters, progressMonitor);
} catch (ZipException e) {
}
}
};
thread.start();
} else {
initAddFiles(fileList, parameters, progressMonitor);
}
}
private void initAddFiles(ArrayList fileList, ZipParameters parameters,
ProgressMonitor progressMonitor) throws ZipException {
if (fileList == null || parameters == null) {
throw new ZipException("one of the input parameters is null when adding files");
}
if(fileList.size() <= 0) {
throw new ZipException("no files to add");
}
if (zipModel.getEndCentralDirRecord() == null) {
zipModel.setEndCentralDirRecord(createEndOfCentralDirectoryRecord());
}
ZipOutputStream outputStream = null;
InputStream inputStream = null;
try {
checkParameters(parameters);
removeFilesIfExists(fileList, parameters, progressMonitor);
boolean isZipFileAlreadExists = Zip4jUtil.checkFileExists(zipModel.getZipFile());
SplitOutputStream splitOutputStream = new SplitOutputStream(new File(zipModel.getZipFile()), zipModel.getSplitLength());
outputStream = new ZipOutputStream(splitOutputStream, this.zipModel);
if (isZipFileAlreadExists) {
if (zipModel.getEndCentralDirRecord() == null) {
throw new ZipException("invalid end of central directory record");
}
splitOutputStream.seek(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir());
}
byte[] readBuff = new byte[InternalZipConstants.BUFF_SIZE];
int readLen = -1;
for (int i = 0; i < fileList.size(); i++) {
if (progressMonitor.isCancelAllTasks()) {
progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
progressMonitor.setState(ProgressMonitor.STATE_READY);
return;
}
ZipParameters fileParameters = (ZipParameters) parameters.clone();
progressMonitor.setFileName(((File)fileList.get(i)).getAbsolutePath());
if (!((File)fileList.get(i)).isDirectory()) {
if (fileParameters.isEncryptFiles() && fileParameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_CALC_CRC);
fileParameters.setSourceFileCRC((int)CRCUtil.computeFileCRC(((File)fileList.get(i)).getAbsolutePath(), progressMonitor));
progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_ADD);
if (progressMonitor.isCancelAllTasks()) {
progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
progressMonitor.setState(ProgressMonitor.STATE_READY);
return;
}
}
if (Zip4jUtil.getFileLengh((File)fileList.get(i)) == 0) {
fileParameters.setCompressionMethod(Zip4jConstants.COMP_STORE);
}
}
outputStream.putNextEntry((File)fileList.get(i), fileParameters);
if (((File)fileList.get(i)).isDirectory()) {
outputStream.closeEntry();
continue;
}
inputStream = new FileInputStream((File)fileList.get(i));
while ((readLen = inputStream.read(readBuff)) != -1) {
if (progressMonitor.isCancelAllTasks()) {
progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
progressMonitor.setState(ProgressMonitor.STATE_READY);
return;
}
outputStream.write(readBuff, 0, readLen);
progressMonitor.updateWorkCompleted(readLen);
}
outputStream.closeEntry();
if (inputStream != null) {
inputStream.close();
}
}
outputStream.finish();
progressMonitor.endProgressMonitorSuccess();
} catch (ZipException e) {
progressMonitor.endProgressMonitorError(e);
throw e;
} catch (Exception e) {
progressMonitor.endProgressMonitorError(e);
throw new ZipException(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
}
}
}
}
public void addStreamToZip(InputStream inputStream, ZipParameters parameters) throws ZipException {
if (inputStream == null || parameters == null) {
throw new ZipException("one of the input parameters is null, cannot add stream to zip");
}
ZipOutputStream outputStream = null;
try {
checkParameters(parameters);
boolean isZipFileAlreadExists = Zip4jUtil.checkFileExists(zipModel.getZipFile());
SplitOutputStream splitOutputStream = new SplitOutputStream(new File(zipModel.getZipFile()), zipModel.getSplitLength());
outputStream = new ZipOutputStream(splitOutputStream, this.zipModel);
if (isZipFileAlreadExists) {
if (zipModel.getEndCentralDirRecord() == null) {
throw new ZipException("invalid end of central directory record");
}
splitOutputStream.seek(zipModel.getEndCentralDirRecord().getOffsetOfStartOfCentralDir());
}
byte[] readBuff = new byte[InternalZipConstants.BUFF_SIZE];
int readLen = -1;
outputStream.putNextEntry(null, parameters);
if (!parameters.getFileNameInZip().endsWith("/") &&
!parameters.getFileNameInZip().endsWith("\\")) {
while ((readLen = inputStream.read(readBuff)) != -1) {
outputStream.write(readBuff, 0, readLen);
}
}
outputStream.closeEntry();
outputStream.finish();
} catch (ZipException e) {
throw e;
} catch (Exception e) {
throw new ZipException(e);
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
//ignore
}
}
}
}
public void addFolderToZip(File file, ZipParameters parameters,
ProgressMonitor progressMonitor, boolean runInThread) throws ZipException {
if (file == null || parameters == null) {
throw new ZipException("one of the input parameters is null, cannot add folder to zip");
}
if (!Zip4jUtil.checkFileExists(file.getAbsolutePath())) {
throw new ZipException("input folder does not exist");
}
if (!file.isDirectory()) {
throw new ZipException("input file is not a folder, user addFileToZip method to add files");
}
if (!Zip4jUtil.checkFileReadAccess(file.getAbsolutePath())) {
throw new ZipException("cannot read folder: " + file.getAbsolutePath());
}
String rootFolderPath = null;
if (parameters.isIncludeRootFolder()) {
if (file.getAbsolutePath() != null) {
rootFolderPath = file.getAbsoluteFile().getParentFile() != null ? file.getAbsoluteFile().getParentFile().getAbsolutePath() : "";
} else {
rootFolderPath = file.getParentFile() != null ? file.getParentFile().getAbsolutePath() : "";
}
} else {
rootFolderPath = file.getAbsolutePath();
}
parameters.setDefaultFolderPath(rootFolderPath);
ArrayList fileList = Zip4jUtil.getFilesInDirectoryRec(file, parameters.isReadHiddenFiles());
if (parameters.isIncludeRootFolder()) {
if (fileList == null) {
fileList = new ArrayList();
}
fileList.add(file);
}
addFiles(fileList, parameters, progressMonitor, runInThread);
}
private void checkParameters(ZipParameters parameters) throws ZipException {
if (parameters == null) {
throw new ZipException("cannot validate zip parameters");
}
if ((parameters.getCompressionMethod() != Zip4jConstants.COMP_STORE) &&
parameters.getCompressionMethod() != Zip4jConstants.COMP_DEFLATE) {
throw new ZipException("unsupported compression type");
}
if (parameters.getCompressionMethod() == Zip4jConstants.COMP_DEFLATE) {
if (parameters.getCompressionLevel() < 0 && parameters.getCompressionLevel() > 9) {
throw new ZipException("invalid compression level. compression level dor deflate should be in the range of 0-9");
}
}
if (parameters.isEncryptFiles()) {
if (parameters.getEncryptionMethod() != Zip4jConstants.ENC_METHOD_STANDARD &&
parameters.getEncryptionMethod() != Zip4jConstants.ENC_METHOD_AES) {
throw new ZipException("unsupported encryption method");
}
if (parameters.getPassword() == null || parameters.getPassword().length <= 0) {
throw new ZipException("input password is empty or null");
}
} else {
parameters.setAesKeyStrength(-1);
parameters.setEncryptionMethod(-1);
}
}
/**
* Before adding a file to a zip file, we check if a file already exists in the zip file
* with the same fileName (including path, if exists). If yes, then we remove this file
* before adding the file<br><br>
*
* <b>Note:</b> Relative path has to be passed as the fileName
*
* @param zipModel
* @param fileName
* @throws ZipException
*/
private void removeFilesIfExists(ArrayList fileList, ZipParameters parameters, ProgressMonitor progressMonitor) throws ZipException {
if (zipModel == null || zipModel.getCentralDirectory() == null ||
zipModel.getCentralDirectory().getFileHeaders() == null ||
zipModel.getCentralDirectory().getFileHeaders().size() <= 0) {
//For a new zip file, this condition satisfies, so do nothing
return;
}
RandomAccessFile outputStream = null;
try {
for (int i = 0; i < fileList.size(); i++) {
File file = (File) fileList.get(i);
String fileName = Zip4jUtil.getRelativeFileName(file.getAbsolutePath(),
parameters.getRootFolderInZip(), parameters.getDefaultFolderPath());
FileHeader fileHeader = Zip4jUtil.getFileHeader(zipModel, fileName);
if (fileHeader != null) {
if (outputStream != null) {
outputStream.close();
outputStream = null;
}
ArchiveMaintainer archiveMaintainer = new ArchiveMaintainer();
progressMonitor.setCurrentOperation(ProgressMonitor.OPERATION_REMOVE);
HashMap retMap = archiveMaintainer.initRemoveZipFile(zipModel,
fileHeader, progressMonitor);
if (progressMonitor.isCancelAllTasks()) {
progressMonitor.setResult(ProgressMonitor.RESULT_CANCELLED);
progressMonitor.setState(ProgressMonitor.STATE_READY);
return;
}
progressMonitor
.setCurrentOperation(ProgressMonitor.OPERATION_ADD);
if (outputStream == null) {
outputStream = prepareFileOutputStream();
if (retMap != null) {
if (retMap.get(InternalZipConstants.OFFSET_CENTRAL_DIR) != null) {
long offsetCentralDir = -1;
try {
offsetCentralDir = Long
.parseLong((String) retMap
.get(InternalZipConstants.OFFSET_CENTRAL_DIR));
} catch (NumberFormatException e) {
throw new ZipException(
"NumberFormatException while parsing offset central directory. " +
"Cannot update already existing file header");
} catch (Exception e) {
throw new ZipException(
"Error while parsing offset central directory. " +
"Cannot update already existing file header");
}
if (offsetCentralDir >= 0) {
outputStream.seek(offsetCentralDir);
}
}
}
}
}
}
} catch (IOException e) {
throw new ZipException(e);
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
//ignore
}
}
}
}
private RandomAccessFile prepareFileOutputStream() throws ZipException {
String outPath = zipModel.getZipFile();
if (!Zip4jUtil.isStringNotNullAndNotEmpty(outPath)) {
throw new ZipException("invalid output path");
}
try {
File outFile = new File(outPath);
if (!outFile.getParentFile().exists()) {
outFile.getParentFile().mkdirs();
}
return new RandomAccessFile(outFile, InternalZipConstants.WRITE_MODE);
} catch (FileNotFoundException e) {
throw new ZipException(e);
}
}
private EndCentralDirRecord createEndOfCentralDirectoryRecord() {
EndCentralDirRecord endCentralDirRecord = new EndCentralDirRecord();
endCentralDirRecord.setSignature(InternalZipConstants.ENDSIG);
endCentralDirRecord.setNoOfThisDisk(0);
endCentralDirRecord.setTotNoOfEntriesInCentralDir(0);
endCentralDirRecord.setTotNoOfEntriesInCentralDirOnThisDisk(0);
endCentralDirRecord.setOffsetOfStartOfCentralDir(0);
return endCentralDirRecord;
}
private long calculateTotalWork(ArrayList fileList, ZipParameters parameters) throws ZipException {
if (fileList == null) {
throw new ZipException("file list is null, cannot calculate total work");
}
long totalWork = 0;
for (int i = 0; i < fileList.size(); i++) {
if(fileList.get(i) instanceof File) {
if (((File)fileList.get(i)).exists()) {
if (parameters.isEncryptFiles() &&
parameters.getEncryptionMethod() == Zip4jConstants.ENC_METHOD_STANDARD) {
totalWork += (Zip4jUtil.getFileLengh((File)fileList.get(i)) * 2);
} else {
totalWork += Zip4jUtil.getFileLengh((File)fileList.get(i));
}
if (zipModel.getCentralDirectory() != null &&
zipModel.getCentralDirectory().getFileHeaders() != null &&
zipModel.getCentralDirectory().getFileHeaders().size() > 0) {
String relativeFileName = Zip4jUtil.getRelativeFileName(
((File)fileList.get(i)).getAbsolutePath(), parameters.getRootFolderInZip(), parameters.getDefaultFolderPath());
FileHeader fileHeader = Zip4jUtil.getFileHeader(zipModel, relativeFileName);
if (fileHeader != null) {
totalWork += (Zip4jUtil.getFileLengh(new File(zipModel.getZipFile())) - fileHeader.getCompressedSize());
}
}
}
}
}
return totalWork;
}
}