Appending Files to a Zip File with Java

Appending files to a zip file with Java

In Java 7 we got Zip File System that allows adding and changing files in zip (jar, war) without manual repackaging.

We can directly write to files inside zip files as in the following example.

Map<String, String> env = new HashMap<>(); 
env.put("create", "true");
Path path = Paths.get("test.zip");
URI uri = URI.create("jar:" + path.toUri());
try (FileSystem fs = FileSystems.newFileSystem(uri, env))
{
Path nf = fs.getPath("new.txt");
try (Writer writer = Files.newBufferedWriter(nf, StandardCharsets.UTF_8, StandardOpenOption.CREATE)) {
writer.write("hello");
}
}

Java appending files into a zip

Alright well here is the final method it's the same method i pastebinned before which i actually got from the stackoverflow topic in the link @Qwe posted before but i added the path variable so that it could add files to folders inside the zip

Alright so now how to use it in my example above i wanted to add a file into a folder that was inside another folder i would do that using my setup in the question like this

private void addFilesToZip(File source, File[] files, String path){
try{
File tmpZip = File.createTempFile(source.getName(), null);
tmpZip.delete();
if(!source.renameTo(tmpZip)){
throw new Exception("Could not make temp file (" + source.getName() + ")");
}
byte[] buffer = new byte[4096];
ZipInputStream zin = new ZipInputStream(new FileInputStream(tmpZip));
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(source));
for(int i = 0; i < files.length; i++){
InputStream in = new FileInputStream(files[i]);
out.putNextEntry(new ZipEntry(path + files[i].getName()));
for(int read = in.read(buffer); read > -1; read = in.read(buffer)){
out.write(buffer, 0, read);
}
out.closeEntry();
in.close();
}
for(ZipEntry ze = zin.getNextEntry(); ze != null; ze = zin.getNextEntry()){
if(!zipEntryMatch(ze.getName(), files, path)){
out.putNextEntry(ze);
for(int read = zin.read(buffer); read > -1; read = zin.read(buffer)){
out.write(buffer, 0, read);
}
out.closeEntry();
}
}
out.close();
tmpZip.delete();
}catch(Exception e){
e.printStackTrace();
}
}

private boolean zipEntryMatch(String zeName, File[] files, String path){
for(int i = 0; i < files.length; i++){
if((path + files[i].getName()).equals(zeName)){
return true;
}
}
return false;
}

Thanks for the link ended up being able to improve that method a bit so that it could add in files that weren't in the root and now i'm a happy camper :) hope this helps someone else out as well

EDIT
I worked a bit more on the method so that it could not only append to the zip but it also is able to update files within the zip

Use the method like this

File[] files = {new File("/path/to/file/to/update/in")};
addFilesToZip(new File("/path/to/zip"), files, "folder/dir/");

You wouldn't start the path (last variable) with / as that's not how it's listed in the zip entries

Java: Add files to zip-file recursively but without full path

Whilst this can be done with the ancient ZipOutputStream I would recommend against it.

It is much more intuitive to think about a Zip archive as a compressed filesystem inside a file, than a stream of bytes. For this reason, Java provides the ZipFileSystem.

So all you need to do is open the Zip as a FileSystem and then manually copy files across.

There are a couple of gotchas:

  1. You need to only copy files, directories need to be created.
  2. The NIO API does not support operations such as relativize across different filesystems (reasons should be obvious) so this you need to do yourself.

Here are a couple of simple methods that will do exactly that:

/**
* This creates a Zip file at the location specified by zip
* containing the full directory tree rooted at contents
*
* @param zip the zip file, this must not exist
* @param contents the root of the directory tree to copy
* @throws IOException, specific exceptions thrown for specific errors
*/
public static void createZip(final Path zip, final Path contents) throws IOException {
if (Files.exists(zip)) {
throw new FileAlreadyExistsException(zip.toString());
}
if (!Files.exists(contents)) {
throw new FileNotFoundException("The location to zip must exist");
}
final Map<String, String> env = new HashMap<>();
//creates a new Zip file rather than attempting to read an existing one
env.put("create", "true");
// locate file system by using the syntax
// defined in java.net.JarURLConnection
final URI uri = URI.create("jar:file:/" + zip.toString().replace("\\", "/"));
try (final FileSystem zipFileSystem = FileSystems.newFileSystem(uri, env);
final Stream<Path> files = Files.walk(contents)) {
final Path root = zipFileSystem.getPath("/");
files.forEach(file -> {
try {
copyToZip(root, contents, file);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}

/**
* Copy a specific file/folder to the zip archive
* If the file is a folder, create the folder. Otherwise copy the file
*
* @param root the root of the zip archive
* @param contents the root of the directory tree being copied, for relativization
* @param file the specific file/folder to copy
*/
private static void copyToZip(final Path root, final Path contents, final Path file) throws IOException {
final Path to = root.resolve(contents.relativize(file).toString());
if (Files.isDirectory(file)) {
Files.createDirectories(to);
} else {
Files.copy(file, to);
}
}

Adding 2 files to zip archive with Java

It looks like you are missing zipOutputStream.closeEntry() at the end of your loop.

Adding files to ZIP file

You can't zip folders, only files. To zip folders, you have to add all the subfiles manually. I wrote this class that does the job. You can have it for free :)

The usage would be this:

List<File> sources = new ArrayList<File>();
sources.add(tobackup);
Packager.packZip(new File(zipName), sources);

Here is the class:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class Packager
{
public static void packZip(File output, List<File> sources) throws IOException
{
System.out.println("Packaging to " + output.getName());
ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(output));
zipOut.setLevel(Deflater.DEFAULT_COMPRESSION);

for (File source : sources)
{
if (source.isDirectory())
{
zipDir(zipOut, "", source);
} else
{
zipFile(zipOut, "", source);
}
}
zipOut.flush();
zipOut.close();
System.out.println("Done");
}

private static String buildPath(String path, String file)
{
if (path == null || path.isEmpty())
{
return file;
} else
{
return path + "/" + file;
}
}

private static void zipDir(ZipOutputStream zos, String path, File dir) throws IOException
{
if (!dir.canRead())
{
System.out.println("Cannot read " + dir.getCanonicalPath() + " (maybe because of permissions)");
return;
}

File[] files = dir.listFiles();
path = buildPath(path, dir.getName());
System.out.println("Adding Directory " + path);

for (File source : files)
{
if (source.isDirectory())
{
zipDir(zos, path, source);
} else
{
zipFile(zos, path, source);
}
}

System.out.println("Leaving Directory " + path);
}

private static void zipFile(ZipOutputStream zos, String path, File file) throws IOException
{
if (!file.canRead())
{
System.out.println("Cannot read " + file.getCanonicalPath() + " (maybe because of permissions)");
return;
}

System.out.println("Compressing " + file.getName());
zos.putNextEntry(new ZipEntry(buildPath(path, file.getName())));

FileInputStream fis = new FileInputStream(file);

byte[] buffer = new byte[4092];
int byteCount = 0;
while ((byteCount = fis.read(buffer)) != -1)
{
zos.write(buffer, 0, byteCount);
System.out.print('.');
System.out.flush();
}
System.out.println();

fis.close();
zos.closeEntry();
}
}

Enjoy!

EDIT: To check if the program is still busy, you can add the three lines I marked with a (*)

EDIT 2: Try the new code. On my platform, it runs correct (OS X). I'm not sure but, there might be some limited read permissions for files in Windows in AppData.

Java - Zipping existing files

Using the inbuilt Java API. This will add a file to a Zip File, this will replace any existing Zip files that may exist, creating a new Zip file.

public class TestZip02 {

public static void main(String[] args) {
try {
zip(new File("TextFiles.zip"), new File("sample.txt"));
} catch (IOException ex) {
ex.printStackTrace();
}
}

public static void zip(File zip, File file) throws IOException {
ZipOutputStream zos = null;
try {
String name = file.getName();
zos = new ZipOutputStream(new FileOutputStream(zip));

ZipEntry entry = new ZipEntry(name);
zos.putNextEntry(entry);

FileInputStream fis = null;
try {
fis = new FileInputStream(file);
byte[] byteBuffer = new byte[1024];
int bytesRead = -1;
while ((bytesRead = fis.read(byteBuffer)) != -1) {
zos.write(byteBuffer, 0, bytesRead);
}
zos.flush();
} finally {
try {
fis.close();
} catch (Exception e) {
}
}
zos.closeEntry();

zos.flush();
} finally {
try {
zos.close();
} catch (Exception e) {
}
}
}
}


Related Topics



Leave a reply



Submit