Java - ZipEntries being removed when appending new ZipEntry - java

i am trying to create a method which copies a java.io.File into a java.util.zip.ZipFile. For this i first open a java.util.zip.ZipOutputStream of the ZipFile, then create a new java.util.zip.ZipEntry with the name of the file, put that new ZipEntry in the ZipOutputStream and then write the content of the File to the ZipOutputStream. After that i flush the ZipOutputStream and close all streams.
For some reason i can't explain this procedure removes all other ZipEntries from the ZipFile and only leaves the copied one in there. Here is the code for copying the file (the file is represented by the java.io.InputStream parameter)
public static boolean compress(final InputStream pSourceStream, final ZipFile pTarget, final ZipEntry pContainer,
final String pFileName) {
if (pSourceStream != null && pTarget != null && pFileName != null && !pFileName.isEmpty()) {
try {
final ZipOutputStream output = new ZipOutputStream(new FileOutputStream(new File(pTarget.getName())));
final ZipEntry target = (pContainer != null) ? new ZipEntry(pContainer.getName() + pFileName)
: new ZipEntry(pFileName);
output.putNextEntry(target);
final byte[] buffer = new byte[BUFFER_SIZE];
int readBytes;
while ((readBytes = pSourceStream.read(buffer)) != -1) {
output.write(buffer, 0, readBytes);
}
pSourceStream.close();
output.flush();
output.close();
return true;
} catch (final IOException ignore) {
// took care of target
// ios will return false
}
}
return false;
}
Here is my code which calls this method
public static void main(final String[] args) throws ZipException, IOException {
final Package p = FileHandler.class.getPackage();
final InputStream classStream = FileHandler.stream(p, "FileHandler.class");
FileHandler.compress(classStream, new ZipFile(new File("C:/Users/litts/Desktop/FileHandlerTest/TestZip.jar")),
"FileHandler1.class");
}
So what im basicly doing here is copying the class-file of FileHandler to a zip-file. But all of its other content is removed when doing so.

You are not reading in the Zip file, you are just overwriting it at the file level.

Using the NIO.2 File API copying files to zip files becomes much easier.
Try (untested):
try (FileSystem zipFS = FileSystems.newFileSystem(URI.create("jar:" + zipURI), Collections.<String, Object>emptyMap())) {
Path targetInZipPath = zipFS.getPath(targetInZipPathString);
Files.copy(srcPath, targetInZipPath);
}

Related

Java program ignoring all the files inside the zip file [duplicate]

This question already has answers here:
How to unzip files recursively in Java?
(10 answers)
Closed last month.
I have program when I give a zip folder path via console. It will go through each item inside that folder (every child item, children of child, etc..). But if it encounters a zip folder it will ignore everything inside the zip folder, I need to read everything including files inside zip folders.
Here is the method that goes through each item:
public static String[] getLogBuffers(String path) throws IOException//path is given via console
{
String zipFileName = path;
String destDirectory = path;
BufferedInputStream errorLogBuffer = null;
BufferedInputStream windowLogBuffer = null;
String strErrorLogFileContents="";
String strWindowLogFileContents="";
String[] errorString=new String[2];
byte[] buffer = new byte[1024];
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName));
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null)
{
String filePath = destDirectory + "/" + zipEntry.getName();
System.out.println("unzipping" + filePath);
if (!zipEntry.isDirectory())
{
if (zipEntry.getName().endsWith("errorlog.txt"))
{
ZipFile zipFile = new ZipFile(path);
InputStream errorStream = zipFile.getInputStream(zipEntry);
BufferedInputStream bufferedInputStream=new BufferedInputStream(errorStream);
byte[] contents = new byte[1024];
System.out.println("ERRORLOG NAME"+zipEntry.getName());
int bytesRead = 0;
while((bytesRead = bufferedInputStream.read(contents)) != -1) {
strErrorLogFileContents += new String(contents, 0, bytesRead);
}
}
if (zipEntry.getName().endsWith("windowlog.txt"))
{ ZipFile zipFile = new ZipFile(path);
InputStream windowStream = zipFile.getInputStream(zipEntry);
BufferedInputStream bufferedInputStream=new BufferedInputStream(windowStream);
byte[] contents = new byte[1024];
System.out.println("WINDOWLOG NAME"+zipEntry.getName());
int bytesRead = 0;
while((bytesRead = bufferedInputStream.read(contents)) != -1) {
strWindowLogFileContents += new String(contents, 0, bytesRead);
}
}
}
zis.closeEntry();
zipEntry = zis.getNextEntry();
}
errorString[0]=strErrorLogFileContents;
errorString[1]=strWindowLogFileContents;
zis.closeEntry();
zis.close();
System.out.println("Buffers ready");
return errorString;
}
Items accessed inside the parent zip folder (my console output):
unzippingC:logFolders/logX3.zip/logX3/
unzippingC:logFolders/logX3.zip/logX3/Anan/
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/2021-11-23_103518.zip
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/errorlog.txt
unzippingC:logX3.zip/logX3/Anan/errorreports/version.txt
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/windowlog.txt
As you can see the program only go until 2021-11-23_103518.zip and goes in another path after that but 2021-11-23_103518.zip has children items(files) that I need to access
appreciate any help, thanks
A zip file is not a folder. Although Windows treats a zip file as if it’s a folder,* it is not a folder. A .zip file is a single file with an internal table of entries, each containing compressed data.
Each inner .zip file you read requires a new ZipFile or ZipInputStream. There is no way around that.
You should not create new ZipFile instances to read the same .zip file’s entries. You only need one ZipFile object. You can go through its entries with its entries() method, and you can read each entry with the ZipFile’s getInputStream method.
(I wouldn’t be surprised if using multiple objects to read the same zip file were to run into file locking problems on Windows.)
try (ZipFile zipFile = new ZipFile(path))
{
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().endsWith("errorlog.txt"))
{
try (InputStream errorStream = zipFile.getInputStream(zipEntry))
{
// ...
}
}
}
}
Notice that no other ZipFile or ZipInputStream objects are created. Only zipFile reads and traverses the file. Also notice the use of a try-with-resources statement to implicitly close the ZipFile and the InputStream.
You should not use += to build a String. Doing so creates a lot of intermediate String objects which will have to be garbage collected, which can hurt your program’s performance. You should wrap each zip entry’s InputStream in an InputStreamReader, then use that Reader’s transferTo method to append to a single StringWriter that holds your combined log.
String strErrorLogFileContents = new StringWriter();
String strWindowLogFileContents = new StringWriter();
try (ZipFile zipFile = new ZipFile(path))
{
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().endsWith("errorlog.txt"))
{
try (Reader entryReader = new InputStreamReader(
zipFile.getInputStream(zipEntry),
StandardCharsets.UTF_8))
{
entryReader.transferTo(strErrorLogFileContents);
}
}
}
}
Notice the use of StandardCharsets.UTF_8. It is almost never correct to create a String from bytes without specifying the Charset. If you don’t provide the Charset, Java will use the system’s default Charset, which means your program will behave differently in Windows than it will on other operating systems.
If you are stuck with Java 8, you won’t have the transferTo method of Reader, so you will have to do the work yourself:
if (zipEntry.getName().endsWith("errorlog.txt"))
{
try (Reader entryReader = new BufferedReader(
new InputStreamReader(
zipFile.getInputStream(zipEntry),
StandardCharsets.UTF_8)))
{
int c;
while ((c = entryReader.read()) >= 0)
{
strErrorLogFileContents.write(c);
}
}
}
The use of BufferedReader means you don’t need to create your own array and implement bulk reads yourself. BufferedReader already does that for you.
As mentioned above, a zip entry which is itself an inner zip file requires a brand new ZipFile or ZipInputStream object to read it. I recommend copying the entry to a temporary file, since reading from a ZipInputStream made from another ZipInputStream is known to be slow, then deleting the temporary file after you’re done reading it.
try (ZipFile zipFile = new ZipFile(path))
{
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().endsWith(".zip"))
{
Path tempZipFile = Files.createTempFile(null, ".zip");
try (InputStream errorStream = zipFile.getInputStream(zipEntry))
{
Files.copy(errorStream, tempZipFile,
StandardCopyOption.REPLACE_EXISTING);
}
String[] logsFromZip = getLogBuffers(tempZipFile.toString());
strErrorLogFileContents.write(logsFromZip[0]);
strWindowLogFileContents.write(logsFromZip[1]);
Files.delete(tempZipFile);
}
}
}
Finally, consider creating a meaningful class for your return value. An array of Strings is difficult to understand. A caller won’t know that it always contains exactly two elements and won’t know what those two elements are. A custom return type would be pretty short:
public class Logs {
private final String errorLog;
private final String windowLog;
public Logs(String errorLog,
String windowLog)
{
this.errorLog = errorLog;
this.windowLog = windowLog;
}
public String getErrorLog()
{
return errorLog;
}
public String getWindowLog()
{
return windowLog;
}
}
As of Java 16, you can use a record to make the declaration much shorter:
public record Logs(String errorLog,
String windowLog)
{ }
Whether you use a record or write out the class, you can use it as a return type in your method:
public static Logs getLogBuffers(String path) throws IOException
{
// ...
return new Logs(
strErrorLogFileContents.toString(),
strWindowLogFileContents.toString());
}
* The Windows explorer shell’s practice of treating zip files as folders is a pretty bad user interface. I know I’m not the only one who thinks so. It often ends up making things more difficult for users instead of easier.

How to read content of files inside Zip file which is inside a Zip file

Zip Structure:-
OuterZip.zip--|
|--Folder1---InnerZip.zip--|
|--TxtFile1.txt // Requirement is to read content of txt file
|--TxtFile2.txt // Without extracting any of zip file
Now i am able to read the names of txt files but not contents of that.
Code:-
public static void main(String arg[]){
ZipFile zip = new ZipFile("Outer.zip");
ZipEntry ze;
for (Enumeration e = zip.entries(); e.hasMoreElements();) {
ZipEntry entry = (ZipEntry) e.nextElement();
ZipInputStream zin = new ZipInputStream(zip.getInputStream(entry));
while ((ze = zin.getNextEntry()) != null)
{
System.out.println(ze.getName()); //Can read names of txtfiles, Not contents
zip.getInputStream(ze); // It is giving null
}
}
}
PS:- 1. Wants to do it without extracting any of zip in file system.
2. Already seen some answers on SOF.
ZipFile zip = new ZipFile("Outer.zip");
...
zip.getInputStream(ze); // It is giving null
Contents of ze (e.g. TxtFile1.txt) are part of InnerZip.zip not Outer.zip (represented by zip), hence null.
I'd use recursion:
public static void main(String[] args) throws IOException {
String name = "Outer.zip";
FileInputStream input = new FileInputStream(new File(name));
readZip(input, name);
}
public static void readZip(final InputStream in, final String name) throws IOException {
final ZipInputStream zin = new ZipInputStream(in);
ZipEntry entry;
while ((entry = zin.getNextEntry()) != null) {
if (entry.getName().toLowerCase().endsWith(".zip")) {
readZip(zin, name + "/" + entry.getName());
} else {
readFile(zin, entry.getName());
}
}
}
private static void readFile(final InputStream in, final String name) {
String contents = new BufferedReader(new InputStreamReader(in)).lines().collect(Collectors.joining("\n"));
System.out.println(String.format("Contents of %s: %s", name, contents));
}
0. while (...) we are iterating through whole all entries.
1. (if (.endsWith(".zip"))) in case we encounter another zip we call recursively itself (readZip()) and go to step 0.
2. (else) otherwise we print the contents of the file (assuming text files here).

Java ZipEntry and Zipoutputstream directory

I have this little piece of code
public void doBuild() throws IOException {
ZipEntry sourceEntry=new ZipEntry(sourcePath);
ZipEntry assetEntry=new ZipEntry(assetPath);
ZipOutputStream out = new ZipOutputStream(new FileOutputStream("output/"+workOn.getName().replaceAll(".bld"," ")+buildNR+".zip"));
out.putNextEntry(sourceEntry);
out.putNextEntry(assetEntry);
out.close();
System.err.println("Build success!");
increaseBuild();
}
So, if I run it it runs trough it fine, creates the .zip and all, but the zip file is empty. sourceEntry and assetEntry are both directories. How could I get those directories to my .zip easily?
For those interested this is a MC mod build system and can be found at https://bitbucket.org/makerimages/makerbuild-system NOTE: the code above is not commited or pushed to there yet!!!!!!!!
Try something like this. The parameter useFullFileNames specifies
whether you want to preserve the full names of the paths to the
files which you're about to zip.
So if you have two files
/dir1/dir2/a.txt
and
/dir1/b.txt
the useFullFileNames specifies if you want to finally see in
the zip file those original paths to the two files or just
the two files with no paths like this
a.txt
and
b.txt
in the root of the zip file which you create.
Note that in my example, the files which are zipped
are actually read and then written to out.
I think you're missing that part.
public static boolean createZip(String fNameZip, boolean useFullFileNames, String... fNames) throws Exception {
try {
int cntBufferSize = 256 * 1024;
BufferedInputStream origin = null;
FileOutputStream dest = new FileOutputStream(fNameZip);
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
byte bBuffer[] = new byte[cntBufferSize];
File ftmp = null;
for (int i = 0; i < fNames.length; i++) {
if (fNames[i] != null) {
FileInputStream fi = new FileInputStream(fNames[i]);
origin = new BufferedInputStream(fi, cntBufferSize);
ftmp = new File(fNames[i]);
ZipEntry entry = new ZipEntry(useFullFileNames ? fNames[i] : ftmp.getName());
out.putNextEntry(entry);
int count;
while ((count = origin.read(bBuffer, 0, cntBufferSize)) != -1) {
out.write(bBuffer, 0, count);
}
origin.close();
}
}
out.close();
return true;
} catch (Exception e) {
return false;
}
}

Cannot delete zip file after unzipping?

I am facing an unusal issue.I am building a tool which is scheduled to run every 5 mins.
It will pick up the zip files from a particular directory and extract files (depending on the file name) to a destination. I am using zipentry to get each filename in zip file and then extracting as required then I back them (zip files, once I finish all the files in a zip) to a particular directory and then delete the zip file. But sometimes (not always) the zip files do not get deleted. Since I am using fileutils.forcedelete(). I am getting an exception: unable to delete file. So I changed to the code to using fileutils.forcedeleteonexit() still some files remain in the source.
Here is a sample of my code:
sourceFile=new file(zipfile);
zipFile = new ZipFile(sourceFile);
zEnum = (Enumeration<ZipEntry>) zipFile.entries();
for (int a = 0; a < zipFile.size(); a++)
{
ZipEntry zE = zEnum.nextElement();
//Function uses zip4j for extracting. No streams used.
extract(String sourceZipFile, String fileNameToExtract, String outputFolder);
}
//I tried it with finally either
zipFile.close();
//Using fileutils to copy. No streams used.
copyFile(sourceFile, backup);
FileUtils.forceDeleteOnExit(sourceFile);
There are no streams used but I am getting a lock on files sometimes (not always).
What seems to be the bug here? Is it the zip4j extraction that is causing the problem or anything else? I am using zip4j 1.3.1.
I think your problem related with OS file buffers, that sometimes are not flushed when you are trying to delete file.
Did you try to use sourceFile.deleteOnExit() instead FileUtils.forceDeleteOnExit(sourceFile)?
Also you can try to check sourceFile.canWrite before deleting (may be it may helps)
You can also try to use FileInputStream() before deleting:
FileInputStream fi = new FileInputStream(sourceFile);
fi.getFD().sync();
Use apache-commons IO's FileDeleteStrategy. Something like:
FileDeleteStrategy.FORCE.delete(file);
Update:
It should be the way IO is being handled in your application. I have written simple code which copies a zip file to a temporary zip, deflates the temporary zip and after few seconds deletes it. Here you go:
public class ZipTest {
private static String dirPath = "/home/ubuntuuser/Desktop/";
public static void main(String[] args) throws Exception {
File myzip = new File(dirPath + "content.zip");
String tempFileStr = dirPath + "content_temp.zip";
File tempFile = new File(tempFileStr);
String unzipFolderStr = dirPath + "unzip";
copyUsingChannels(myzip, tempFile);
// copyUsingStreams(myzip, tempFile);
unZip(tempFileStr, unzipFolderStr);
Thread.sleep(3000);
tempFile.delete();
}
private static void copyUsingStreams(File myzip, File tempFile)
throws IOException, FileNotFoundException {
byte[] barray = new byte[1024];
if (!tempFile.exists())
{
tempFile.createNewFile();
}
FileOutputStream fos = new FileOutputStream(tempFile);
FileInputStream fis = new FileInputStream(myzip);
int length = 0;
while ((length = fis.read(barray)) != -1)
{
fos.write(barray, 0, length);
}
fis.close();
fos.close();
}
public static void copyUsingChannels(final File srcFile, final File destFile) throws Exception
{
if (!destFile.exists())
{
destFile.createNewFile();
}
FileChannel source = new FileInputStream(srcFile).getChannel();
FileChannel destination = new FileOutputStream(destFile).getChannel();
source.transferTo(0, source.size(), destination);
source.close();
destination.close();
}
private static void unZip(String zipFile, String outputFolder) throws Exception {
byte[] buffer = new byte[1024];
File folder = new File(outputFolder);
if (!folder.exists()) {
folder.mkdir();
}
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
ZipEntry ze = zis.getNextEntry();
while (ze != null) {
String fileName = ze.getName();
File newFile = new File(outputFolder + File.separator + fileName);
System.out.println("file unzip : " + newFile.getAbsoluteFile());
new File(newFile.getParent()).mkdirs();
if (ze.isDirectory())
{
newFile.mkdir();
ze = zis.getNextEntry();
continue;
}
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
ze = zis.getNextEntry();
}
zis.closeEntry();
zis.close();
}
}

How to decompress a zip archive which has sub directories?

Say I have a zip file MyZipFile.zip which contains (1) a file MyFile.txt and (2) a folder MyFolder which contains a file MyFileInMyFolder.txt, i.e. something as follows:
MyZipFile.zip
|-> MyFile.txt
|-> MyFolder
|-> MyFileInMyFolder.txt
I want to decompress this zip archive. The most common code sample I have been able to find searching online uses the ZipInputStream class somewhat like the code pasted at the bottom of this question. The problem with this however, using the example above, is that it will create MyFolder but will not decompress the contents of MyFolder. Anyone know whether it is possible to decompress the contents of a folder in a zip archive using ZipInputStream or by any other means?
public static boolean unzip(File sourceZipFile, File targetFolder)
{
// pre-stuff
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(sourceZipFile));
ZipEntry zipEntry = null;
while ((zipEntry = zipInputStream.getNextEntry()) != null)
{
File zipEntryFile = new File(targetFolder, zipEntry.getName());
if (zipEntry.isDirectory())
{
if (!zipEntryFile.exists() && !zipEntryFile.mkdirs())
return false;
}
else
{
FileOutputStream fileOutputStream = new FileOutputStream(zipEntryFile);
byte buffer[] = new byte[1024];
int count;
while ((count = zipInputStream.read(buffer, 0, buffer.length)) != -1)
fileOutputStream.write(buffer, 0, count);
fileOutputStream.flush();
fileOutputStream.close();
zipInputStream.closeEntry();
}
}
zipInputStream.close();
// post-stuff
}
Try this:
ZipInputStream zis = null;
try {
zis = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// Create a file on HDD in the destinationPath directory
// destinationPath is a "root" folder, where you want to extract your ZIP file
File entryFile = new File(destinationPath, entry.getName());
if (entry.isDirectory()) {
if (entryFile.exists()) {
logger.log(Level.WARNING, "Directory {0} already exists!", entryFile);
} else {
entryFile.mkdirs();
}
} else {
// Make sure all folders exists (they should, but the safer, the better ;-))
if (entryFile.getParentFile() != null && !entryFile.getParentFile().exists()) {
entryFile.getParentFile().mkdirs();
}
// Create file on disk...
if (!entryFile.exists()) {
entryFile.createNewFile();
}
// and rewrite data from stream
OutputStream os = null;
try {
os = new FileOutputStream(entryFile);
IOUtils.copy(zis, os);
} finally {
IOUtils.closeQuietly(os);
}
}
}
} finally {
IOUtils.closeQuietly(zis);
}
Note, that it uses Apache Commons IO to handle stream copying / closing.

Categories