* 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:
parent
375c5eea58
commit
28260e51d7
|
@ -14,6 +14,7 @@ import java.util.prefs.BackingStoreException;
|
|||
import java.util.prefs.Preferences;
|
||||
|
||||
import net.filebot.UserFiles.FileChooser;
|
||||
import net.filebot.archive.Archive.Extractor;
|
||||
import net.filebot.cli.ArgumentBean;
|
||||
import net.filebot.util.ExceptionUtilities;
|
||||
import net.filebot.util.PreferencesList;
|
||||
|
@ -112,6 +113,10 @@ public final class Settings {
|
|||
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() {
|
||||
try {
|
||||
String threadPool = System.getProperty("threadPool");
|
||||
|
|
|
@ -3,13 +3,8 @@ 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 java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
@ -17,105 +12,52 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import net.filebot.MediaTypes;
|
||||
import net.filebot.Settings;
|
||||
import net.filebot.util.FileUtilities.ExtensionFileFilter;
|
||||
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 {
|
||||
|
||||
private ISevenZipInArchive inArchive;
|
||||
private ArchiveOpenVolumeCallback openVolume;
|
||||
public static enum Extractor {
|
||||
SevenZipNativeBindings, SevenZipExecutable;
|
||||
|
||||
public Archive(File file) throws Exception {
|
||||
// initialize 7-Zip-JBinding
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
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);
|
||||
public ArchiveExtractor newInstance(File archive) throws Exception {
|
||||
switch (this) {
|
||||
case SevenZipNativeBindings:
|
||||
return new SevenZipNativeBindings(archive);
|
||||
case SevenZipExecutable:
|
||||
return new SevenZipExecutable(archive);
|
||||
}
|
||||
} catch (InvocationTargetException e) {
|
||||
throw (Exception) e.getTargetException();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int itemCount() throws SevenZipException {
|
||||
return inArchive.getNumberOfItems();
|
||||
public static Archive open(File archive) throws Exception {
|
||||
return new Archive(Settings.getPreferredArchiveExtractor().newInstance(archive));
|
||||
}
|
||||
|
||||
public Map<PropID, Object> getItem(int index) throws SevenZipException {
|
||||
Map<PropID, Object> item = new EnumMap<PropID, Object>(PropID.class);
|
||||
private final ArchiveExtractor extractor;
|
||||
|
||||
for (PropID prop : PropID.values()) {
|
||||
Object value = inArchive.getProperty(index, prop);
|
||||
if (value != null) {
|
||||
item.put(prop, value);
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
public Archive(ArchiveExtractor extractor) throws Exception {
|
||||
this.extractor = extractor;
|
||||
}
|
||||
|
||||
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;
|
||||
public List<FileInfo> listFiles() throws Exception {
|
||||
return extractor.listFiles();
|
||||
}
|
||||
|
||||
public void extract(ExtractOutProvider outputMapper) throws SevenZipException {
|
||||
inArchive.extract(null, false, new ExtractCallback(inArchive, outputMapper));
|
||||
public void extract(File outputDir) throws Exception {
|
||||
extractor.extract(outputDir);
|
||||
}
|
||||
|
||||
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));
|
||||
public void extract(File outputDir, FileFilter filter) throws Exception {
|
||||
extractor.extract(outputDir, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
inArchive.close();
|
||||
} catch (SevenZipException e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
openVolume.close();
|
||||
if (extractor instanceof Closeable) {
|
||||
((Closeable) extractor).close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,9 +68,7 @@ public class Archive implements Closeable {
|
|||
extensions.addAll(MediaTypes.getDefault().getExtensionList("archive"));
|
||||
|
||||
// formats provided by the library
|
||||
for (ArchiveFormat it : ArchiveFormat.values()) {
|
||||
extensions.add(it.getMethodName());
|
||||
}
|
||||
extensions.addAll(SevenZipNativeBindings.getArchiveTypes());
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
@ -141,8 +81,8 @@ public class Archive implements Closeable {
|
|||
|
||||
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 FileFilter archives = new ExtensionFileFilter(getArchiveTypes());
|
||||
private final Pattern volume = Pattern.compile("[.]r[0-9]+$|[.]part[0-9]+|[.][0-9]+$", Pattern.CASE_INSENSITIVE);
|
||||
private final FileFilter archives = new ExtensionFileFilter(getArchiveTypes());
|
||||
|
||||
@Override
|
||||
public boolean accept(File path) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -1,40 +1,38 @@
|
|||
|
||||
package net.filebot.archive;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
||||
public class FileMapper implements ExtractOutProvider {
|
||||
|
||||
|
||||
private File outputDir;
|
||||
private boolean flatten;
|
||||
|
||||
|
||||
|
||||
public FileMapper(File outputDir, boolean flatten) {
|
||||
this.outputDir = outputDir;
|
||||
this.flatten = flatten;
|
||||
};
|
||||
|
||||
|
||||
|
||||
public File getOutputDir() {
|
||||
return outputDir;
|
||||
}
|
||||
|
||||
public File getOutputFile(File entry) {
|
||||
return new File(outputDir, flatten ? entry.getName() : entry.getPath());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public OutputStream getStream(File entry) throws IOException {
|
||||
File outputFile = getOutputFile(entry);
|
||||
File outputFolder = outputFile.getParentFile();
|
||||
|
||||
|
||||
// create parent folder if necessary
|
||||
if (!outputFolder.isDirectory() && !outputFolder.mkdirs()) {
|
||||
throw new IOException("Failed to create folder: " + outputFolder);
|
||||
}
|
||||
|
||||
|
||||
return new FileOutputStream(outputFile);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1127,7 +1127,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||
List<File> extractedFiles = new ArrayList<File>();
|
||||
|
||||
for (File file : archiveFiles) {
|
||||
Archive archive = new Archive(file);
|
||||
Archive archive = Archive.open(file);
|
||||
try {
|
||||
File outputFolder = new File(output != null ? output : getName(file));
|
||||
if (!outputFolder.isAbsolute()) {
|
||||
|
@ -1169,7 +1169,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||
CLILogger.finest("Extracting files " + outputMapping);
|
||||
|
||||
// extract all files
|
||||
archive.extract(outputMapper);
|
||||
archive.extract(outputMapper.getOutputDir());
|
||||
|
||||
for (FileInfo it : outputMapping) {
|
||||
extractedFiles.add(it.toFile());
|
||||
|
@ -1178,7 +1178,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||
CLILogger.finest("Extracting files " + selection);
|
||||
|
||||
// extract files selected by the given filter
|
||||
archive.extract(outputMapper, new FileFilter() {
|
||||
archive.extract(outputMapper.getOutputDir(), new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File entry) {
|
||||
|
|
|
@ -100,7 +100,7 @@ public class MediaDetection {
|
|||
|
||||
public static boolean isVideoDiskFile(File file) throws Exception {
|
||||
FileFilter diskFolderEntryFilter = releaseInfo.getDiskFolderEntryFilter();
|
||||
Archive iso = new Archive(file);
|
||||
Archive iso = Archive.open(file);
|
||||
try {
|
||||
for (FileInfo it : iso.listFiles()) {
|
||||
for (File entry : listPath(it.toFile())) {
|
||||
|
|
|
@ -82,7 +82,7 @@ class ExtractTool extends Tool<TableModel> {
|
|||
for (File file : files) {
|
||||
// ignore non-archives files and trailing multi-volume parts
|
||||
if (Archive.VOLUME_ONE_FILTER.accept(file)) {
|
||||
Archive archive = new Archive(file);
|
||||
Archive archive = Archive.open(file);
|
||||
try {
|
||||
for (FileInfo it : archive.listFiles()) {
|
||||
entries.add(new ArchiveEntry(file, it));
|
||||
|
@ -247,7 +247,7 @@ class ExtractTool extends Tool<TableModel> {
|
|||
// update progress dialog
|
||||
firePropertyChange("currentFile", null, file);
|
||||
|
||||
Archive archive = new Archive(file);
|
||||
Archive archive = Archive.open(file);
|
||||
try {
|
||||
final FileMapper outputMapper = new FileMapper(outputFolder, false);
|
||||
|
||||
|
@ -281,10 +281,10 @@ class ExtractTool extends Tool<TableModel> {
|
|||
if (!skip || conflictAction == ConflictAction.OVERRIDE) {
|
||||
if (filter == null || forceExtractAll) {
|
||||
// extract all files
|
||||
archive.extract(outputMapper);
|
||||
archive.extract(outputMapper.getOutputDir());
|
||||
} else {
|
||||
// extract files selected by the given filter
|
||||
archive.extract(outputMapper, new FileFilter() {
|
||||
archive.extract(outputMapper.getOutputDir(), new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File entry) {
|
||||
|
|
Loading…
Reference in New Issue