Skip to content

Commit

Permalink
Add support for .7z
Browse files Browse the repository at this point in the history
  • Loading branch information
jjlauer committed Dec 12, 2024
1 parent 859f51a commit c54aa2c
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public class ArchiveFormats {
new ArchiveFormat("tar", "bzip2", ".tar.bz2"),
new ArchiveFormat("tar", "xz", ".tar.xz"),
new ArchiveFormat("tar", "zstd", ".tar.zst"),
new ArchiveFormat(null, "gz", ".gz")
new ArchiveFormat(null, "gz", ".gz"),
new ArchiveFormat("7z", null, ".7z")
);

static public ArchiveFormat detectByFileName(String fileName) {
Expand Down
166 changes: 96 additions & 70 deletions blaze-archive/src/main/java/com/fizzed/blaze/archive/Unarchive.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,52 +24,47 @@
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.compress.compressors.FileNameUtil;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.function.Predicate;

public class Unarchive extends Action<Unarchive.Result,Void> implements VerbosityMixin<Unarchive> {

static public class Result extends com.fizzed.blaze.core.Result<Unarchive,Void,Result> {

private int dirsCreated;
private int filesCopied;
private int filesOverwritten;
private int fileCount;

Result(Unarchive action, Void value) {
super(action, value);
}

public int getDirsCreated() {
return dirsCreated;
}

public int getFilesCopied() {
return filesCopied;
}

public int getFilesOverwritten() {
return filesOverwritten;
public int getFileCount() {
return fileCount;
}

}

private final VerboseLogger log;
private final Path source;
private Path target;
private Boolean stripLeadingPath;
//private Predicate<String> filter;
//private boolean force;
private Verbosity verbosity;

public Unarchive(Context context, Path source) {
super(context);
this.log = new VerboseLogger(this);
this.source = source;
this.stripLeadingPath = false;
//this.force = false;
}

Expand All @@ -93,6 +88,16 @@ public Unarchive target(Path path) {
return this;
}

public Unarchive stripLeadingPath() {
this.stripLeadingPath = true;
return this;
}

public Unarchive stripLeadingPath(Boolean stripLeadingPath) {
this.stripLeadingPath = stripLeadingPath;
return this;
}

/*public Unarchive force() {
this.force = true;
return this;
Expand All @@ -105,10 +110,6 @@ public Unarchive force(boolean force) {

@Override
protected Unarchive.Result doRun() throws BlazeException {
/*if (this.sources.isEmpty() && !this.force) {
throw new BlazeException("Copy requires at least 1 source path (and force is disabled)");
}*/

// the source must all exist (we should check this first before we do anything)
if (!Files.exists(this.source)) {
throw new FileNotFoundException("Source file " + source + " not found");
Expand Down Expand Up @@ -139,71 +140,96 @@ protected Unarchive.Result doRun() throws BlazeException {
log.debug("Unarchiving {} -> {}", this.source, this.target);
log.debug("Detected archive format: archiveMethod={}, compressMethod={}", archiveFormat.getArchiveMethod(), archiveFormat.getCompressMethod());

try (InputStream fin = Files.newInputStream(this.source)) {
// we need it buffered so we can auto-detect the format with mark/reset
try (InputStream bin = new BufferedInputStream(fin)) {
// is this file compressed?
InputStream uncompressedIn = bin;
if (archiveFormat.getCompressMethod() != null) {
try {
uncompressedIn = CompressorStreamFactory.getSingleton().createCompressorInputStream(archiveFormat.getCompressMethod(), bin);
} catch (CompressorException e) {
throw new BlazeException("Unable to uncompress source", e);
}
}

try {
// is this file archived?
if (archiveFormat.getArchiveMethod() != null) {
// special handling for 7z
if ("7z".equals(archiveFormat.getArchiveMethod())) {
this.unarchive7z(this.source, destDir);
} else {
// unarchiving via streaming
try (InputStream fin = Files.newInputStream(this.source)) {
// we need it buffered so we can auto-detect the format with mark/reset
try (InputStream bin = new BufferedInputStream(fin)) {
// is this file compressed?
InputStream uncompressedIn = bin;
if (archiveFormat.getCompressMethod() != null) {
try {
ArchiveInputStream<? extends ArchiveEntry> ais = ArchiveStreamFactory.DEFAULT.createArchiveInputStream(archiveFormat.getArchiveMethod(), uncompressedIn);

ArchiveEntry entry = ais.getNextEntry();
while (entry != null) {
final Path file = entry.resolveIn(destDir);

if (entry.isDirectory()) {
Files.createDirectories(file);
} else {
log.debug(entry.getName());
Files.createDirectories(file.getParent());
Files.copy(ais, file, StandardCopyOption.REPLACE_EXISTING);
}

entry = ais.getNextEntry();
}
} catch (ArchiveException e) {
throw new BlazeException("Unable to unarchive source", e);
uncompressedIn = CompressorStreamFactory.getSingleton().createCompressorInputStream(archiveFormat.getCompressMethod(), bin);
} catch (CompressorException e) {
throw new BlazeException("Unable to uncompress source", e);
}
}
} finally {
if (uncompressedIn != null) {
uncompressedIn.close();

try {
// is this file archived?
if (archiveFormat.getArchiveMethod() != null) {
this.unarchiveStreaming(archiveFormat.getArchiveMethod(), uncompressedIn, destDir);
}
} finally {
if (uncompressedIn != null) {
uncompressedIn.close();
}
}
}
} catch (IOException e) {
throw new BlazeException("Unable to copy", e);
}
}

//log.debug("Copied {} files, overwrote {} files, created {} dirs (in {})", result.filesCopied, result.filesOverwritten, result.dirsCreated, timer);

return new Unarchive.Result(this, null);
}

private void unarchiveStreaming(String archiveMethod, InputStream in, Path destDir) throws BlazeException {
try {
ArchiveInputStream<? extends ArchiveEntry> ais = ArchiveStreamFactory.DEFAULT.createArchiveInputStream(archiveMethod, in);

ArchiveEntry entry = ais.getNextEntry();
while (entry != null) {
this.extractEntry(entry, ais, destDir);
entry = ais.getNextEntry();
}
} catch (IOException | ArchiveException e) {
throw new BlazeException("Failed to unarchive: " + e.getMessage(), e);
}
}

private void unarchive7z(Path source, Path destDir) throws BlazeException {
try (SevenZFile sevenZFile = new SevenZFile(source.toFile())) {
SevenZArchiveEntry entry = sevenZFile.getNextEntry();
while (entry != null) {
try (InputStream in = sevenZFile.getInputStream(entry)) {
this.extractEntry(entry, in, destDir);
}

/*ArchiveInputStream ain = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.ZIP, is);
ZipArchiveEntry entry = (ZipArchiveEntry) in.getNextEntry();
OutputStream out = Files.newOutputStream(dir.toPath().resolve(entry.getName()));
IOUtils.copy(in, out);
out.close();
in.close();final InputStream is = Files.newInputStream(input.toPath());
ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.ZIP, is);
ZipArchiveEntry entry = (ZipArchiveEntry) in.getNextEntry();
OutputStream out = Files.newOutputStream(dir.toPath().resolve(entry.getName()));
IOUtils.copy(in, out);
out.close();
in.close();*/
entry = sevenZFile.getNextEntry();
}
} catch (IOException e) {
throw new BlazeException("Unable to copy", e);
throw new BlazeException("Failed to unarchive: " + e.getMessage(), e);
}
}

//log.debug("Copied {} files, overwrote {} files, created {} dirs (in {})", result.filesCopied, result.filesOverwritten, result.dirsCreated, timer);
private void extractEntry(ArchiveEntry entry, InputStream in, Path destDir) throws BlazeException {
try {
if (!entry.isDirectory()) {
String name = entry.getName();

return new Unarchive.Result(this, null);
if (this.stripLeadingPath != null && this.stripLeadingPath) {
int slashPos = name.indexOf('/');
if (slashPos > 0) {
name = name.substring(slashPos+1);
}
log.debug("{} (stripped leading)", name);
} else {
log.debug("{}", name);
}

Path file = destDir.resolve(name).normalize();
Files.createDirectories(file.getParent());
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
throw new BlazeException("Failed to unarchive: " + e.getMessage(), e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ public void zipNoRootDir() throws Exception {
assertThat(Files.exists(target.resolve("c/d.txt")), is(true));
}

@Test
public void stripLeadingPath() throws Exception {
final Path file = Resources.file("/fixtures/sample-with-root-dir.zip");
final Path target = this.createEmptyTargetDir("zipWithRootDir");

new Unarchive(this.context, file)
.target(target)
.stripLeadingPath()
.run();

assertThat(Files.exists(target.resolve("a.txt")), is(true));
assertThat(Files.exists(target.resolve("c/d.txt")), is(true));
}

@Test
public void tarNoRootDir() throws Exception {
final Path file = Resources.file("/fixtures/sample-no-root-dir.tar");
Expand Down Expand Up @@ -116,4 +130,17 @@ public void tarZstNoRootDir() throws Exception {
assertThat(Files.exists(target.resolve("c/d.txt")), is(true));
}

@Test
public void _7zWithRootDir() throws Exception {
final Path file = Resources.file("/fixtures/sample-with-root-dir.7z");
final Path target = this.createEmptyTargetDir("_7zWithRootDir");

new Unarchive(this.context, file)
.target(target)
.run();

assertThat(Files.exists(target.resolve("sample/a.txt")), is(true));
assertThat(Files.exists(target.resolve("sample/c/d.txt")), is(true));
}

}
Binary file not shown.

0 comments on commit c54aa2c

Please sign in to comment.