package net.filebot.ui.filter;
import static net.filebot.Logging.*;
import static net.filebot.UserFiles.*;
import static net.filebot.util.FileUtilities.*;
import static net.filebot.util.ui.SwingUI.*;
import java.awt.Color;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import net.filebot.ResourceManager;
import net.filebot.archive.Archive;
import net.filebot.archive.FileMapper;
import net.filebot.cli.ConflictAction;
import net.filebot.util.FileUtilities;
import net.filebot.util.ui.GradientStyle;
import net.filebot.util.ui.LoadingOverlayPane;
import net.filebot.util.ui.ProgressMonitor;
import net.filebot.util.ui.ProgressMonitor.ProgressWorker;
import net.filebot.util.ui.notification.SeparatorBorder;
import net.filebot.vfs.FileInfo;
import net.filebot.vfs.SimpleFileInfo;
import net.miginfocom.swing.MigLayout;
class ExtractTool extends Tool<TableModel> {
private JTable table = new JTable(new ArchiveEntryModel());
public ExtractTool() {
super("Archives");
table.setAutoCreateRowSorter(true);
table.setAutoCreateColumnsFromModel(true);
table.setFillsViewportHeight(true);
table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setBackground(Color.white);
table.setGridColor(new Color(0xEEEEEE));
table.setRowHeight(25);
JScrollPane tableScrollPane = new JScrollPane(table);
tableScrollPane.setBorder(new SeparatorBorder(2, new Color(0, 0, 0, 90), GradientStyle.TOP_TO_BOTTOM, SeparatorBorder.Position.BOTTOM));
setLayout(new MigLayout("insets 0, nogrid, fill", "align center", "[fill][pref!]"));
add(new LoadingOverlayPane(tableScrollPane, this, "25px", "30px"), "grow, wrap");
add(new JButton(extractAction), "gap top rel, gap bottom unrel");
}
@Override
protected void setModel(TableModel model) {
table.setModel(model);
}
@Override
protected TableModel createModelInBackground(List<File> root) throws Exception {
if (root.isEmpty()) {
return new ArchiveEntryModel();
}
// ignore non-archives files and trailing multi-volume parts
List<File> files = listFiles(root, Archive.VOLUME_ONE_FILTER, HUMAN_NAME_ORDER);
List<ArchiveEntry> entries = new ArrayList<ArchiveEntry>();
for (File file : files) {
try (Archive archive = Archive.open(file)) {
for (FileInfo it : archive.listFiles()) {
entries.add(new ArchiveEntry(file, it));
}
}
// unwind thread, if we have been cancelled
if (Thread.interrupted()) {
throw new CancellationException();
}
}
return new ArchiveEntryModel(entries);
}
private Action extractAction = newAction("Extract All", ResourceManager.getIcon("package.extract"), evt -> {
List<File> archives = ((ArchiveEntryModel) table.getModel()).getArchiveList();
if (archives.isEmpty())
return;
File selectedFile = showOpenDialogSelectFolder(archives.get(0).getParentFile(), "Extract to ...", evt);
if (selectedFile == null)
return;
ExtractWorker worker = new ExtractWorker(archives, selectedFile, null, true, ConflictAction.AUTO);
ProgressMonitor.runTask("Extract", "Extracting files...", worker);
});
private static class ArchiveEntry {
public final File archive;
public final FileInfo entry;
public ArchiveEntry(File archive, FileInfo entry) {
this.archive = archive;
this.entry = entry;
}
}
private static class ArchiveEntryModel extends AbstractTableModel {
private final ArchiveEntry[] data;
public ArchiveEntryModel() {
this.data = new ArchiveEntry[0];
}
public ArchiveEntryModel(Collection<ArchiveEntry> data) {
this.data = data.toArray(new ArchiveEntry[data.size()]);
}
public List<File> getArchiveList() {
Set<File> archives = new LinkedHashSet<File>();
for (ArchiveEntry it : data) {
archives.add(it.archive);
}
return new ArrayList<File>(archives);
}
@Override
public int getRowCount() {
return data.length;
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public String getColumnName(int column) {
switch (column) {
case 0:
return "File";
case 1:
return "Path";
case 2:
return "Size";
}
return null;
}
@Override
public Object getValueAt(int row, int column) {
switch (column) {
case 0:
return data[row].entry.getName();
case 1:
File root = new File(data[row].archive.getName());
File prefix = data[row].entry.toFile().getParentFile();
File path = (prefix == null) ? root : new File(root, prefix.getPath());
return normalizePathSeparators(path.getPath());
case 2:
return FileUtilities.formatSize(data[row].entry.getLength());
}
return null;
}
}
private static class ExtractWorker implements ProgressWorker<Void> {
private final File[] archives;
private final File outputFolder;
private final FileFilter filter;
private final boolean forceExtractAll;
private final ConflictAction conflictAction;
public ExtractWorker(Collection<File> archives, File outputFolder, FileFilter filter, boolean forceExtractAll, ConflictAction conflictAction) {
this.archives = archives.toArray(new File[archives.size()]);
this.outputFolder = outputFolder;
this.filter = filter;
this.forceExtractAll = forceExtractAll;
this.conflictAction = conflictAction;
}
@Override
public Void call(Consumer<String> message, BiConsumer<Long, Long> progress, Supplier<Boolean> cancelled) throws Exception {
for (File file : archives) {
try {
// update progress dialog
message.accept(String.format("Extracting %s", file.getName()));
Archive archive = Archive.open(file);
try {
final FileMapper outputMapper = new FileMapper(outputFolder);
final List<FileInfo> outputMapping = new ArrayList<FileInfo>();
for (FileInfo it : archive.listFiles()) {
File outputPath = outputMapper.getOutputFile(it.toFile());
outputMapping.add(new SimpleFileInfo(outputPath.getPath(), it.getLength()));
}
final Set<FileInfo> selection = new TreeSet<FileInfo>();
for (FileInfo future : outputMapping) {
if (filter == null || filter.accept(future.toFile())) {
selection.add(future);
}
}
// check if there is anything to extract at all
if (selection.isEmpty()) {
continue;
}
boolean skip = true;
for (FileInfo future : filter == null || forceExtractAll ? outputMapping : selection) {
if (conflictAction == ConflictAction.AUTO) {
skip &= (future.toFile().exists() && future.getLength() == future.toFile().length());
} else {
skip &= (future.toFile().exists());
}
}
if (!skip || conflictAction == ConflictAction.OVERRIDE) {
if (filter == null || forceExtractAll) {
// extract all files
archive.extract(outputMapper.getOutputDir());
} else {
// extract files selected by the given filter
archive.extract(outputMapper.getOutputDir(), new FileFilter() {
@Override
public boolean accept(File entry) {
return selection.contains(outputMapper.getOutputFile(entry));
}
});
}
}
} finally {
archive.close();
}
} catch (Exception e) {
log.log(Level.WARNING, "Failed to extract archive: " + file.getName(), e);
}
if (cancelled.get()) {
throw new CancellationException("Extract cancelled");
}
}
return null;
}
}
}