1
0
mirror of https://github.com/mitb-archive/filebot synced 2024-11-10 11:25:04 -05:00

* support for delegating 7z extract & list operation to the "7z" cmdline tool rathern than the native bindings which are default

This commit is contained in:
Reinhard Pointner 2015-03-25 22:38:15 +00:00
parent 375c5eea58
commit 28260e51d7
9 changed files with 300 additions and 106 deletions

View File

@ -14,6 +14,7 @@ import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import net.filebot.UserFiles.FileChooser; import net.filebot.UserFiles.FileChooser;
import net.filebot.archive.Archive.Extractor;
import net.filebot.cli.ArgumentBean; import net.filebot.cli.ArgumentBean;
import net.filebot.util.ExceptionUtilities; import net.filebot.util.ExceptionUtilities;
import net.filebot.util.PreferencesList; import net.filebot.util.PreferencesList;
@ -112,6 +113,10 @@ public final class Settings {
return FileChooser.valueOf(System.getProperty("net.filebot.UserFiles.fileChooser", "Swing")); return FileChooser.valueOf(System.getProperty("net.filebot.UserFiles.fileChooser", "Swing"));
} }
public static Extractor getPreferredArchiveExtractor() {
return Extractor.valueOf(System.getProperty("net.filebot.Archive.extractor", "SevenZipNativeBindings"));
}
public static int getPreferredThreadPoolSize() { public static int getPreferredThreadPoolSize() {
try { try {
String threadPool = System.getProperty("threadPool"); String threadPool = System.getProperty("threadPool");

View File

@ -3,13 +3,8 @@ package net.filebot.archive;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
@ -17,105 +12,52 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import net.filebot.MediaTypes; import net.filebot.MediaTypes;
import net.filebot.Settings;
import net.filebot.util.FileUtilities.ExtensionFileFilter; import net.filebot.util.FileUtilities.ExtensionFileFilter;
import net.filebot.vfs.FileInfo; import net.filebot.vfs.FileInfo;
import net.filebot.vfs.SimpleFileInfo;
import net.sf.sevenzipjbinding.ArchiveFormat;
import net.sf.sevenzipjbinding.ISevenZipInArchive;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZipException;
public class Archive implements Closeable { public class Archive implements Closeable {
private ISevenZipInArchive inArchive; public static enum Extractor {
private ArchiveOpenVolumeCallback openVolume; SevenZipNativeBindings, SevenZipExecutable;
public Archive(File file) throws Exception { public ArchiveExtractor newInstance(File archive) throws Exception {
// initialize 7-Zip-JBinding switch (this) {
if (!file.exists()) { case SevenZipNativeBindings:
throw new FileNotFoundException(file.getAbsolutePath()); return new SevenZipNativeBindings(archive);
} case SevenZipExecutable:
return new SevenZipExecutable(archive);
try {
openVolume = new ArchiveOpenVolumeCallback();
if (!hasMultiPartIndex(file)) {
// single volume archives and multi-volume rar archives
inArchive = SevenZipLoader.open(openVolume.getStream(file.getAbsolutePath()), openVolume);
} else {
// raw multi-volume archives
inArchive = SevenZipLoader.open(new VolumedArchiveInStream(file.getAbsolutePath(), openVolume), null);
} }
} catch (InvocationTargetException e) { return null;
throw (Exception) e.getTargetException();
} }
} }
public int itemCount() throws SevenZipException { public static Archive open(File archive) throws Exception {
return inArchive.getNumberOfItems(); return new Archive(Settings.getPreferredArchiveExtractor().newInstance(archive));
} }
public Map<PropID, Object> getItem(int index) throws SevenZipException { private final ArchiveExtractor extractor;
Map<PropID, Object> item = new EnumMap<PropID, Object>(PropID.class);
for (PropID prop : PropID.values()) { public Archive(ArchiveExtractor extractor) throws Exception {
Object value = inArchive.getProperty(index, prop); this.extractor = extractor;
if (value != null) {
item.put(prop, value);
}
}
return item;
} }
public List<FileInfo> listFiles() throws SevenZipException { public List<FileInfo> listFiles() throws Exception {
List<FileInfo> paths = new ArrayList<FileInfo>(); return extractor.listFiles();
for (int i = 0; i < inArchive.getNumberOfItems(); i++) {
boolean isFolder = (Boolean) inArchive.getProperty(i, PropID.IS_FOLDER);
if (!isFolder) {
String path = (String) inArchive.getProperty(i, PropID.PATH);
Long length = (Long) inArchive.getProperty(i, PropID.SIZE);
if (path != null) {
paths.add(new SimpleFileInfo(path, length != null ? length : -1));
}
}
}
return paths;
} }
public void extract(ExtractOutProvider outputMapper) throws SevenZipException { public void extract(File outputDir) throws Exception {
inArchive.extract(null, false, new ExtractCallback(inArchive, outputMapper)); extractor.extract(outputDir);
} }
public void extract(ExtractOutProvider outputMapper, FileFilter filter) throws SevenZipException { public void extract(File outputDir, FileFilter filter) throws Exception {
List<Integer> selection = new ArrayList<Integer>(); extractor.extract(outputDir, filter);
for (int i = 0; i < inArchive.getNumberOfItems(); i++) {
boolean isFolder = (Boolean) inArchive.getProperty(i, PropID.IS_FOLDER);
if (!isFolder) {
String path = (String) inArchive.getProperty(i, PropID.PATH);
if (path != null && filter.accept(new File(path))) {
selection.add(i);
}
}
}
int[] indices = new int[selection.size()];
for (int i = 0; i < indices.length; i++) {
indices[i] = selection.get(i);
}
inArchive.extract(indices, false, new ExtractCallback(inArchive, outputMapper));
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
try { if (extractor instanceof Closeable) {
inArchive.close(); ((Closeable) extractor).close();
} catch (SevenZipException e) {
throw new IOException(e);
} finally {
openVolume.close();
} }
} }
@ -126,9 +68,7 @@ public class Archive implements Closeable {
extensions.addAll(MediaTypes.getDefault().getExtensionList("archive")); extensions.addAll(MediaTypes.getDefault().getExtensionList("archive"));
// formats provided by the library // formats provided by the library
for (ArchiveFormat it : ArchiveFormat.values()) { extensions.addAll(SevenZipNativeBindings.getArchiveTypes());
extensions.add(it.getMethodName());
}
return extensions; return extensions;
} }
@ -141,8 +81,8 @@ public class Archive implements Closeable {
public static final FileFilter VOLUME_ONE_FILTER = new FileFilter() { public static final FileFilter VOLUME_ONE_FILTER = new FileFilter() {
private Pattern volume = Pattern.compile("[.]r[0-9]+$|[.]part[0-9]+|[.][0-9]+$", Pattern.CASE_INSENSITIVE); private final Pattern volume = Pattern.compile("[.]r[0-9]+$|[.]part[0-9]+|[.][0-9]+$", Pattern.CASE_INSENSITIVE);
private FileFilter archives = new ExtensionFileFilter(getArchiveTypes()); private final FileFilter archives = new ExtensionFileFilter(getArchiveTypes());
@Override @Override
public boolean accept(File path) { public boolean accept(File path) {

View File

@ -0,0 +1,17 @@
package net.filebot.archive;
import java.io.File;
import java.io.FileFilter;
import java.util.List;
import net.filebot.vfs.FileInfo;
public interface ArchiveExtractor {
public List<FileInfo> listFiles() throws Exception;
public void extract(File outputDir) throws Exception;
public void extract(File outputDir, FileFilter filter) throws Exception;
}

View File

@ -1,30 +1,28 @@
package net.filebot.archive; package net.filebot.archive;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
public class FileMapper implements ExtractOutProvider { public class FileMapper implements ExtractOutProvider {
private File outputDir; private File outputDir;
private boolean flatten; private boolean flatten;
public FileMapper(File outputDir, boolean flatten) { public FileMapper(File outputDir, boolean flatten) {
this.outputDir = outputDir; this.outputDir = outputDir;
this.flatten = flatten; this.flatten = flatten;
}; };
public File getOutputDir() {
return outputDir;
}
public File getOutputFile(File entry) { public File getOutputFile(File entry) {
return new File(outputDir, flatten ? entry.getName() : entry.getPath()); return new File(outputDir, flatten ? entry.getName() : entry.getPath());
} }
@Override @Override
public OutputStream getStream(File entry) throws IOException { public OutputStream getStream(File entry) throws IOException {
File outputFile = getOutputFile(entry); File outputFile = getOutputFile(entry);

View File

@ -0,0 +1,98 @@
package net.filebot.archive;
import static java.nio.charset.StandardCharsets.*;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import net.filebot.util.ByteBufferOutputStream;
import net.filebot.vfs.FileInfo;
import net.filebot.vfs.SimpleFileInfo;
public class SevenZipExecutable implements ArchiveExtractor {
// e.g. 2014-09-15 05:33:10 ....A 398536 625065 folder/file.txt
final Pattern listFilesLinePattern = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}\\s+\\S+\\s+(?<size>\\d+)\\s+\\d*\\s+(?<name>.+)$", Pattern.MULTILINE);
final File archive;
public SevenZipExecutable(File file) throws Exception {
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
this.archive = file.getCanonicalFile();
}
protected String get7zCommand() {
// use 7z executable path as specified by the cmdline or default to "7z" and let the shell figure it out
return System.getProperty("net.filebot.Archive.7z", "7z");
}
protected CharSequence execute(String... command) throws IOException {
Process process = new ProcessBuilder(command).redirectError(Redirect.INHERIT).start();
ByteBufferOutputStream bb = new ByteBufferOutputStream(8 * 1024);
bb.transferFully(process.getInputStream());
try {
int returnCode = process.waitFor();
CharSequence output = UTF_8.decode(bb.getByteBuffer());
// DEBUG
// System.out.println("Execute: " + Arrays.asList(command));
// System.out.println(output);
if (returnCode == 0) {
return output;
} else {
throw new IOException(String.format("%s failed with exit code %d: %s", get7zCommand(), returnCode, output.toString().replaceAll("\\s+", " ").trim()));
}
} catch (InterruptedException e) {
throw new IOException(String.format("%s timed out", get7zCommand()), e);
}
}
public List<FileInfo> listFiles() throws IOException {
List<FileInfo> paths = new ArrayList<FileInfo>();
// e.g. 7z l -y archive.7z
CharSequence output = execute(get7zCommand(), "l", "-y", archive.getPath());
Matcher m = listFilesLinePattern.matcher(output);
while (m.find()) {
String path = m.group("name").trim();
long size = Long.parseLong(m.group("size"));
// ignore folders, e.g. 2015-03-26 02:37:24 D.... 0 0 folder
if (size > 0 && path.length() > 0) {
paths.add(new SimpleFileInfo(path, size));
}
}
return paths;
}
public void extract(File outputDir) throws IOException {
// e.g. 7z x -y -aos archive.7z
execute(get7zCommand(), "x", "-y", "-aos", archive.getPath(), "-o" + outputDir.getCanonicalPath());
}
public void extract(File outputDir, FileFilter filter) throws IOException {
// e.g. 7z x -y -aos archive.7z file.txt image.png info.nfo
Stream<String> command = Stream.of(get7zCommand(), "x", "-y", "-aos", archive.getPath(), "-o" + outputDir.getCanonicalPath());
Stream<String> selection = listFiles().stream().filter(f -> filter.accept(f.toFile())).map(f -> f.getPath());
execute(Stream.concat(command, selection).toArray(String[]::new));
}
}

View File

@ -0,0 +1,136 @@
package net.filebot.archive;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import net.filebot.vfs.FileInfo;
import net.filebot.vfs.SimpleFileInfo;
import net.sf.sevenzipjbinding.ArchiveFormat;
import net.sf.sevenzipjbinding.ISevenZipInArchive;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZipException;
public class SevenZipNativeBindings implements ArchiveExtractor, Closeable {
private ISevenZipInArchive inArchive;
private ArchiveOpenVolumeCallback openVolume;
public SevenZipNativeBindings(File file) throws Exception {
// initialize 7-Zip-JBinding
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
try {
openVolume = new ArchiveOpenVolumeCallback();
if (!Archive.hasMultiPartIndex(file)) {
// single volume archives and multi-volume rar archives
inArchive = SevenZipLoader.open(openVolume.getStream(file.getAbsolutePath()), openVolume);
} else {
// raw multi-volume archives
inArchive = SevenZipLoader.open(new VolumedArchiveInStream(file.getAbsolutePath(), openVolume), null);
}
} catch (InvocationTargetException e) {
throw (Exception) e.getTargetException();
}
}
public int itemCount() throws SevenZipException {
return inArchive.getNumberOfItems();
}
public Map<PropID, Object> getItem(int index) throws SevenZipException {
Map<PropID, Object> item = new EnumMap<PropID, Object>(PropID.class);
for (PropID prop : PropID.values()) {
Object value = inArchive.getProperty(index, prop);
if (value != null) {
item.put(prop, value);
}
}
return item;
}
public List<FileInfo> listFiles() throws SevenZipException {
List<FileInfo> paths = new ArrayList<FileInfo>();
for (int i = 0; i < inArchive.getNumberOfItems(); i++) {
boolean isFolder = (Boolean) inArchive.getProperty(i, PropID.IS_FOLDER);
if (!isFolder) {
String path = (String) inArchive.getProperty(i, PropID.PATH);
Long length = (Long) inArchive.getProperty(i, PropID.SIZE);
if (path != null) {
paths.add(new SimpleFileInfo(path, length != null ? length : -1));
}
}
}
return paths;
}
@Override
public void extract(File outputDir) throws Exception {
extract(new FileMapper(outputDir, false));
}
@Override
public void extract(File outputDir, FileFilter filter) throws Exception {
extract(new FileMapper(outputDir, false), filter);
}
public void extract(ExtractOutProvider outputMapper) throws SevenZipException {
inArchive.extract(null, false, new ExtractCallback(inArchive, outputMapper));
}
public void extract(ExtractOutProvider outputMapper, FileFilter filter) throws SevenZipException {
List<Integer> selection = new ArrayList<Integer>();
for (int i = 0; i < inArchive.getNumberOfItems(); i++) {
boolean isFolder = (Boolean) inArchive.getProperty(i, PropID.IS_FOLDER);
if (!isFolder) {
String path = (String) inArchive.getProperty(i, PropID.PATH);
if (path != null && filter.accept(new File(path))) {
selection.add(i);
}
}
}
int[] indices = new int[selection.size()];
for (int i = 0; i < indices.length; i++) {
indices[i] = selection.get(i);
}
inArchive.extract(indices, false, new ExtractCallback(inArchive, outputMapper));
}
@Override
public void close() throws IOException {
try {
inArchive.close();
} catch (SevenZipException e) {
throw new IOException(e);
} finally {
openVolume.close();
}
}
public static List<String> getArchiveTypes() {
List<String> extensions = new ArrayList<String>();
// formats provided by the library
for (ArchiveFormat it : ArchiveFormat.values()) {
extensions.add(it.getMethodName());
}
return extensions;
}
}

View File

@ -1127,7 +1127,7 @@ public class CmdlineOperations implements CmdlineInterface {
List<File> extractedFiles = new ArrayList<File>(); List<File> extractedFiles = new ArrayList<File>();
for (File file : archiveFiles) { for (File file : archiveFiles) {
Archive archive = new Archive(file); Archive archive = Archive.open(file);
try { try {
File outputFolder = new File(output != null ? output : getName(file)); File outputFolder = new File(output != null ? output : getName(file));
if (!outputFolder.isAbsolute()) { if (!outputFolder.isAbsolute()) {
@ -1169,7 +1169,7 @@ public class CmdlineOperations implements CmdlineInterface {
CLILogger.finest("Extracting files " + outputMapping); CLILogger.finest("Extracting files " + outputMapping);
// extract all files // extract all files
archive.extract(outputMapper); archive.extract(outputMapper.getOutputDir());
for (FileInfo it : outputMapping) { for (FileInfo it : outputMapping) {
extractedFiles.add(it.toFile()); extractedFiles.add(it.toFile());
@ -1178,7 +1178,7 @@ public class CmdlineOperations implements CmdlineInterface {
CLILogger.finest("Extracting files " + selection); CLILogger.finest("Extracting files " + selection);
// extract files selected by the given filter // extract files selected by the given filter
archive.extract(outputMapper, new FileFilter() { archive.extract(outputMapper.getOutputDir(), new FileFilter() {
@Override @Override
public boolean accept(File entry) { public boolean accept(File entry) {

View File

@ -100,7 +100,7 @@ public class MediaDetection {
public static boolean isVideoDiskFile(File file) throws Exception { public static boolean isVideoDiskFile(File file) throws Exception {
FileFilter diskFolderEntryFilter = releaseInfo.getDiskFolderEntryFilter(); FileFilter diskFolderEntryFilter = releaseInfo.getDiskFolderEntryFilter();
Archive iso = new Archive(file); Archive iso = Archive.open(file);
try { try {
for (FileInfo it : iso.listFiles()) { for (FileInfo it : iso.listFiles()) {
for (File entry : listPath(it.toFile())) { for (File entry : listPath(it.toFile())) {

View File

@ -82,7 +82,7 @@ class ExtractTool extends Tool<TableModel> {
for (File file : files) { for (File file : files) {
// ignore non-archives files and trailing multi-volume parts // ignore non-archives files and trailing multi-volume parts
if (Archive.VOLUME_ONE_FILTER.accept(file)) { if (Archive.VOLUME_ONE_FILTER.accept(file)) {
Archive archive = new Archive(file); Archive archive = Archive.open(file);
try { try {
for (FileInfo it : archive.listFiles()) { for (FileInfo it : archive.listFiles()) {
entries.add(new ArchiveEntry(file, it)); entries.add(new ArchiveEntry(file, it));
@ -247,7 +247,7 @@ class ExtractTool extends Tool<TableModel> {
// update progress dialog // update progress dialog
firePropertyChange("currentFile", null, file); firePropertyChange("currentFile", null, file);
Archive archive = new Archive(file); Archive archive = Archive.open(file);
try { try {
final FileMapper outputMapper = new FileMapper(outputFolder, false); final FileMapper outputMapper = new FileMapper(outputFolder, false);
@ -281,10 +281,10 @@ class ExtractTool extends Tool<TableModel> {
if (!skip || conflictAction == ConflictAction.OVERRIDE) { if (!skip || conflictAction == ConflictAction.OVERRIDE) {
if (filter == null || forceExtractAll) { if (filter == null || forceExtractAll) {
// extract all files // extract all files
archive.extract(outputMapper); archive.extract(outputMapper.getOutputDir());
} else { } else {
// extract files selected by the given filter // extract files selected by the given filter
archive.extract(outputMapper, new FileFilter() { archive.extract(outputMapper.getOutputDir(), new FileFilter() {
@Override @Override
public boolean accept(File entry) { public boolean accept(File entry) {