Skip to content

Commit

Permalink
#84 Remove multiple files from zip
Browse files Browse the repository at this point in the history
  • Loading branch information
srikanth-lingala committed Mar 4, 2020
1 parent f512a55 commit b2306ee
Show file tree
Hide file tree
Showing 13 changed files with 405 additions and 308 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ the zip, then you can use `zipFile.getFileHeader("root_folder/entry_name_in_zip.
new ZipFile("filename.zip").removeFile("fileNameInZipToRemove");
~~~~

If `fileNameInZipToRemove` represents a folder. All the files and folders under this folder will be removed as well
(this is valid since v2.5.0 of zip4j. All prior versions remove just the single entry even if it is a folder).

Please note that the file name is relative the root folder in zip. That is, if the file you want to remove exists in a
folder called "folder1", which in-turn exists in a folder called "root-folder", removing this file from zip can be done
as below:
Expand All @@ -266,6 +269,20 @@ if (fileHeader == null) {
zipFile.removeFile(fileHeader);
~~~~

Since v2.5.0 of zip4j, it is possible to remove multiple files and folders from a zip file. You can now pass in a list
as shown in the code below:

~~~~
ZipFile zipFile = new ZipFile("someZip.zip");
List<String> filesToRemove = Arrays.asList("file1.txt", "file2.txt", "some-folder/", "some-new-folder-1/somefile.pdf");
zipFile.removeFiles(filesToRemove);
~~~~

The above code will remove `file1.txt`, `file2.txt`, all files and folders under `some-folder` (including `some-folder`)
and just the entry `somefile.pdf` in folder `some-new-folder-1`. All other files and folders are kept intact in the zip
file.

### Rename entries in the zip file

There are three ways to rename an entry in a zip file with zip4j. One way is to pass in a file header and the new file
Expand Down
75 changes: 47 additions & 28 deletions src/main/java/net/lingala/zip4j/ZipFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@
import net.lingala.zip4j.tasks.ExtractFileTask.ExtractFileTaskParameters;
import net.lingala.zip4j.tasks.MergeSplitZipFileTask;
import net.lingala.zip4j.tasks.MergeSplitZipFileTask.MergeSplitZipFileTaskParameters;
import net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask;
import net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.RemoveEntryFromZipFileTaskParameters;
import net.lingala.zip4j.tasks.RenameFileTask;
import net.lingala.zip4j.tasks.RenameFileTask.RenameFileTaskParameters;
import net.lingala.zip4j.tasks.RemoveFilesFromZipTask;
import net.lingala.zip4j.tasks.RemoveFilesFromZipTask.RemoveFilesFromZipTaskParameters;
import net.lingala.zip4j.tasks.RenameFilesTask;
import net.lingala.zip4j.tasks.RenameFilesTask.RenameFilesTaskParameters;
import net.lingala.zip4j.tasks.SetCommentTask;
import net.lingala.zip4j.tasks.SetCommentTask.SetCommentTaskTaskParameters;
import net.lingala.zip4j.util.FileUtils;
Expand Down Expand Up @@ -635,49 +635,68 @@ public boolean isSplitArchive() throws ZipException {
return zipModel.isSplitArchive();
}

/**
* Removes the file provided in the input file header from the zip file.
*
* If zip file is a split zip file, then this method throws an exception as
* zip specification does not allow for updating split zip archives.
*
* If this file header is a directory, all files and directories
* under this directory will be removed as well.
*
* @param fileHeader
* @throws ZipException
*/
public void removeFile(FileHeader fileHeader) throws ZipException {
if (fileHeader == null) {
throw new ZipException("input file header is null, cannot remove file");
}

removeFile(fileHeader.getFileName());
}

/**
* Removes the file provided in the input parameters from the zip file.
* This method first finds the file header and then removes the file.
*
* If file does not exist, then this method throws an exception.
*
* If zip file is a split zip file, then this method throws an exception as
* zip specification does not allow for updating split zip archives.
*
* If the entry representing this file name is a directory, all files and directories
* under this directory will be removed as well.
*
* @param fileName
* @throws ZipException
*/
public void removeFile(String fileName) throws ZipException {

if (!isStringNotNullAndNotEmpty(fileName)) {
throw new ZipException("file name is empty or null, cannot remove file");
}

if (zipModel == null) {
readZipInfo();
}

if (zipModel.isSplitArchive()) {
throw new ZipException("Zip file format does not allow updating split/spanned files");
}

FileHeader fileHeader = HeaderUtil.getFileHeader(zipModel, fileName);
if (fileHeader == null) {
throw new ZipException("could not find file header for file: " + fileName);
}

removeFile(fileHeader);
removeFiles(Collections.singletonList(fileName));
}

/**
* Removes the file provided in the input file header from the zip file.
* Removes all files from the zip file that match the names in the input list.
*
* If any of the file is a directory, all the files and directories under this directory
* will be removed as well
*
* If zip file is a split zip file, then this method throws an exception as
* zip specification does not allow for updating split zip archives.
*
* @param fileHeader
* @param fileNames
* @throws ZipException
*/
public void removeFile(FileHeader fileHeader) throws ZipException {
if (fileHeader == null) {
throw new ZipException("input file header is null, cannot remove file");
public void removeFiles(List<String> fileNames) throws ZipException {
if (fileNames == null) {
throw new ZipException("fileNames list is null");
}

if (fileNames.isEmpty()) {
return;
}

if (zipModel == null) {
Expand All @@ -688,8 +707,8 @@ public void removeFile(FileHeader fileHeader) throws ZipException {
throw new ZipException("Zip file format does not allow updating split/spanned files");
}

new RemoveEntryFromZipFileTask(zipModel, buildAsyncParameters()).execute(
new RemoveEntryFromZipFileTaskParameters(fileHeader, charset));
new RemoveFilesFromZipTask(zipModel, headerWriter, buildAsyncParameters()).execute(
new RemoveFilesFromZipTaskParameters(fileNames, charset));
}

/**
Expand Down Expand Up @@ -770,8 +789,8 @@ public void renameFiles(Map<String, String> fileNamesMap) throws ZipException {
}

AsyncZipTask.AsyncTaskParameters asyncTaskParameters = buildAsyncParameters();
new RenameFileTask(zipModel, headerWriter, new RawIO(), charset, asyncTaskParameters).execute(
new RenameFileTaskParameters(fileNamesMap));
new RenameFilesTask(zipModel, headerWriter, new RawIO(), charset, asyncTaskParameters).execute(
new RenameFilesTaskParameters(fileNamesMap));
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/net/lingala/zip4j/headers/HeaderUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ public static String decodeStringWithCharset(byte[] data, boolean isUtf8Encoded,
}
}

public static int getLocalFileHeaderSize(FileHeader fileHeader) {
return 30 + fileHeader.getFileNameLength() + fileHeader.getExtraFieldLength(); // 30 = all fixed lengths in local file header
}

private static FileHeader getFileHeaderWithExactMatch(ZipModel zipModel, String fileName) throws ZipException {
if (zipModel == null) {
throw new ZipException("zip model is null, cannot determine file header with exact match for fileName: "
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/net/lingala/zip4j/model/AbstractFileHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,17 @@ public boolean isDirectory() {
public void setDirectory(boolean directory) {
isDirectory = directory;
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}

if (!(obj instanceof AbstractFileHeader)) {
return false;
}

return this.getFileName().equals(((AbstractFileHeader) obj).getFileName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.RemoveEntryFromZipFileTaskParameters;
import net.lingala.zip4j.tasks.RemoveFilesFromZipTask.RemoveFilesFromZipTaskParameters;
import net.lingala.zip4j.util.BitUtils;
import net.lingala.zip4j.util.FileUtils;
import net.lingala.zip4j.util.InternalZipConstants;
Expand All @@ -22,6 +22,7 @@
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static net.lingala.zip4j.headers.HeaderUtil.getFileHeader;
Expand Down Expand Up @@ -261,10 +262,10 @@ private List<File> removeFilesIfExists(List<File> files, ZipParameters zipParame
return filesToAdd;
}

private void removeFile(FileHeader fileHeader, ProgressMonitor progressMonitor, Charset charset) throws ZipException {
void removeFile(FileHeader fileHeader, ProgressMonitor progressMonitor, Charset charset) throws ZipException {
AsyncTaskParameters asyncTaskParameters = new AsyncTaskParameters(null, false, progressMonitor);
RemoveEntryFromZipFileTask removeEntryFromZipFileTask = new RemoveEntryFromZipFileTask(zipModel, asyncTaskParameters);
removeEntryFromZipFileTask.execute(new RemoveEntryFromZipFileTaskParameters(fileHeader, charset));
RemoveFilesFromZipTask removeFilesFromZipTask = new RemoveFilesFromZipTask(zipModel, headerWriter, asyncTaskParameters);
removeFilesFromZipTask.execute(new RemoveFilesFromZipTaskParameters(Collections.singletonList(fileHeader.getFileName()), charset));
}

private String replaceFileNameInZip(String fileInZipWithPath, String newFileName) {
Expand Down
35 changes: 21 additions & 14 deletions src/main/java/net/lingala/zip4j/tasks/AbstractModifyFileTask.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.lingala.zip4j.tasks;

import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.headers.HeaderUtil;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipModel;
import net.lingala.zip4j.progress.ProgressMonitor;
Expand All @@ -10,6 +11,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.List;
import java.util.Random;

abstract class AbstractModifyFileTask<T> extends AsyncZipTask<T> {
Expand All @@ -29,32 +31,37 @@ File getTemporaryFile(String zipPathWithName) {
return tmpFile;
}

long getOffsetLocalFileHeader(FileHeader fileHeader) {
long offsetLocalFileHeader = fileHeader.getOffsetLocalHeader();
void updateOffsetsForAllSubsequentFileHeaders(ZipModel zipModel, FileHeader fileHeaderModified, long offsetToAdd) throws ZipException {
int indexOfFileHeader = HeaderUtil.getIndexOfFileHeader(zipModel, fileHeaderModified);

if (fileHeader.getZip64ExtendedInfo() != null && fileHeader.getZip64ExtendedInfo().getOffsetLocalHeader() != -1) {
offsetLocalFileHeader = fileHeader.getZip64ExtendedInfo().getOffsetLocalHeader();
if (indexOfFileHeader == -1) {
throw new ZipException("Could not locate modified file header in zipModel");
}

return offsetLocalFileHeader;
}
List<FileHeader> allFileHeaders = zipModel.getCentralDirectory().getFileHeaders();

long getOffsetOfStartOfCentralDirectory(ZipModel zipModel) {
long offsetStartCentralDir = zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory();
for (int i = indexOfFileHeader + 1; i < allFileHeaders.size(); i++) {
FileHeader fileHeaderToUpdate = allFileHeaders.get(i);
fileHeaderToUpdate.setOffsetLocalHeader(fileHeaderToUpdate.getOffsetLocalHeader() + offsetToAdd);

if (zipModel.isZip64Format() && zipModel.getZip64EndOfCentralDirectoryRecord() != null) {
offsetStartCentralDir = zipModel.getZip64EndOfCentralDirectoryRecord()
.getOffsetStartCentralDirectoryWRTStartDiskNumber();
}
if (zipModel.isZip64Format()
&& fileHeaderToUpdate.getZip64ExtendedInfo() != null
&& fileHeaderToUpdate.getZip64ExtendedInfo().getOffsetLocalHeader() != -1) {

return offsetStartCentralDir;
fileHeaderToUpdate.getZip64ExtendedInfo().setOffsetLocalHeader(
fileHeaderModified.getZip64ExtendedInfo().getOffsetLocalHeader() + offsetToAdd
);
}
}
}

void cleanupFile(boolean successFlag, File zipFile, File temporaryZipFile) throws ZipException {
if (successFlag) {
restoreFileName(zipFile, temporaryZipFile);
} else {
temporaryZipFile.delete();
if (!temporaryZipFile.delete()) {
throw new ZipException("Could not delete temporary file");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.progress.ProgressMonitor;
import net.lingala.zip4j.tasks.AddStreamToZipTask.AddStreamToZipTaskParameters;
import net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.RemoveEntryFromZipFileTaskParameters;
import net.lingala.zip4j.util.Zip4jUtil;

import java.io.IOException;
Expand Down Expand Up @@ -83,9 +82,7 @@ private void removeFileIfExists(ZipModel zipModel, Charset charset, String fileN

FileHeader fileHeader = HeaderUtil.getFileHeader(zipModel, fileNameInZip);
if (fileHeader != null) {
AsyncTaskParameters asyncTaskParameters = new AsyncTaskParameters(null, false, progressMonitor);
RemoveEntryFromZipFileTask removeEntryFromZipFileTask = new RemoveEntryFromZipFileTask(zipModel, asyncTaskParameters);
removeEntryFromZipFileTask.execute(new RemoveEntryFromZipFileTaskParameters(fileHeader, charset));
removeFile(fileHeader, progressMonitor, charset);
}
}

Expand Down
Loading

0 comments on commit b2306ee

Please sign in to comment.