Refactor Filter / Tools

This commit is contained in:
Reinhard Pointner 2016-11-02 00:07:08 +08:00
parent fcf3bd75f2
commit 8656af9508
10 changed files with 144 additions and 131 deletions

View File

@ -8,6 +8,7 @@ import java.awt.Color;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
@ -48,10 +49,10 @@ class AttributeTool extends Tool<TableModel> {
}
@Override
protected TableModel createModelInBackground(File root) {
protected TableModel createModelInBackground(List<File> root) {
FileAttributesTableModel model = new FileAttributesTableModel();
if (root == null) {
if (root.isEmpty()) {
return model;
}
@ -74,6 +75,10 @@ class AttributeTool extends Tool<TableModel> {
model.addRow(String.format("%s::%d", "OMDb", movie.getImdbId()), metaObject, originalName, file);
}
}
if (Thread.interrupted()) {
throw new CancellationException();
}
}
return model;

View File

@ -2,11 +2,10 @@ package net.filebot.ui.filter;
import static net.filebot.Logging.*;
import static net.filebot.UserFiles.*;
import static net.filebot.util.ExceptionUtilities.*;
import static net.filebot.util.FileUtilities.*;
import static net.filebot.util.ui.SwingUI.*;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
@ -21,7 +20,6 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JScrollPane;
@ -76,8 +74,8 @@ class ExtractTool extends Tool<TableModel> {
}
@Override
protected TableModel createModelInBackground(File root) throws InterruptedException {
if (root == null) {
protected TableModel createModelInBackground(List<File> root) throws Exception {
if (root.isEmpty()) {
return new ArchiveEntryModel();
}
@ -85,7 +83,6 @@ class ExtractTool extends Tool<TableModel> {
List<File> files = listFiles(root, Archive.VOLUME_ONE_FILTER, HUMAN_NAME_ORDER);
List<ArchiveEntry> entries = new ArrayList<ArchiveEntry>();
try {
for (File file : files) {
try (Archive archive = Archive.open(file)) {
for (FileInfo it : archive.listFiles()) {
@ -95,25 +92,15 @@ class ExtractTool extends Tool<TableModel> {
// unwind thread, if we have been cancelled
if (Thread.interrupted()) {
throw new InterruptedException();
throw new CancellationException();
}
}
} catch (Exception e) {
// unwind thread, if we have been cancelled
if (findCause(e, InterruptedException.class) != null) {
throw findCause(e, InterruptedException.class);
}
log.log(Level.WARNING, e.getMessage(), e);
}
return new ArchiveEntryModel(entries);
}
private Action extractAction = new AbstractAction("Extract All", ResourceManager.getIcon("package.extract")) {
@Override
public void actionPerformed(ActionEvent evt) {
final List<File> archives = ((ArchiveEntryModel) table.getModel()).getArchiveList();
private Action extractAction = newAction("Extract All", ResourceManager.getIcon("package.extract"), evt -> {
List<File> archives = ((ArchiveEntryModel) table.getModel()).getArchiveList();
if (archives.isEmpty())
return;
@ -123,8 +110,7 @@ class ExtractTool extends Tool<TableModel> {
ExtractWorker worker = new ExtractWorker(archives, selectedFile, null, true, ConflictAction.AUTO);
ProgressMonitor.runTask("Extract", "Extracting files...", worker);
}
};
});
private static class ArchiveEntry {
@ -199,7 +185,7 @@ class ExtractTool extends Tool<TableModel> {
}
protected static class ExtractWorker implements ProgressWorker<Void> {
private static class ExtractWorker implements ProgressWorker<Void> {
private final File[] archives;
private final File outputFolder;

View File

@ -8,7 +8,6 @@ import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
@ -54,8 +53,20 @@ public class FileTree extends JTree {
return (DefaultTreeModel) super.getModel();
}
public FolderNode getRoot() {
return (FolderNode) getModel().getRoot();
public List<File> getRoot() {
FolderNode model = (FolderNode) getModel().getRoot();
return model.getChildren().stream().map(node -> {
if (node instanceof FolderNode) {
FolderNode folder = (FolderNode) node;
return folder.getFile();
}
if (node instanceof FileNode) {
FileNode file = (FileNode) node;
return file.getFile();
}
return null;
}).collect(toList());
}
public void clear() {
@ -258,15 +269,16 @@ public class FileTree extends JTree {
private final String title;
private final List<TreeNode> children;
/**
* Creates a root node (no parent, no title, empty list of children)
*/
public FolderNode() {
this(null, "", new ArrayList<TreeNode>(0));
this(emptyList()); // empty root node
}
public FolderNode(List<TreeNode> children) {
this(null, "/", children); // root node
}
public FolderNode(String title, List<TreeNode> children) {
this(null, title, children);
this(null, title, children); // virtual node
}
public FolderNode(File file, String title, List<TreeNode> children) {
@ -318,8 +330,9 @@ public class FileTree extends JTree {
@Override
protected Iterator<TreeNode> children(TreeNode node) {
if (node instanceof FolderNode)
if (node instanceof FolderNode) {
return ((FolderNode) node).getChildren().iterator();
}
// can't step into non-folder nodes
return null;
@ -333,8 +346,9 @@ public class FileTree extends JTree {
@Override
protected File filter(TreeNode node) {
if (node instanceof FileNode)
if (node instanceof FileNode) {
return ((FileNode) node).getFile();
}
// filter out non-file nodes
return null;

View File

@ -63,8 +63,10 @@ class FileTreePanel extends JComponent {
fireFileTreeChange();
});
public static final String FILE_TREE_PROPERTY = "FILE_TREE";
private void fireFileTreeChange() {
firePropertyChange("filetree", null, fileTree);
firePropertyChange(FILE_TREE_PROPERTY, null, fileTree);
}
}

View File

@ -2,14 +2,15 @@ package net.filebot.ui.filter;
import static net.filebot.Logging.*;
import static net.filebot.Settings.*;
import static net.filebot.util.ExceptionUtilities.*;
import static net.filebot.util.FileUtilities.*;
import static net.filebot.util.ui.SwingUI.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import javax.swing.tree.TreeNode;
@ -18,7 +19,6 @@ import net.filebot.mac.MacAppUtilities;
import net.filebot.ui.filter.FileTree.FileNode;
import net.filebot.ui.filter.FileTree.FolderNode;
import net.filebot.ui.transfer.BackgroundFileTransferablePolicy;
import net.filebot.util.ExceptionUtilities;
import net.filebot.util.FastFile;
class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<TreeNode> {
@ -42,46 +42,36 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<TreeNo
@Override
protected void process(List<TreeNode> root) {
tree.getModel().setRoot(root.get(0));
tree.getModel().setRoot(new FolderNode(root));
tree.getModel().reload();
}
@Override
protected void process(Exception e) {
log.log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
}
@Override
protected void handleInBackground(List<File> files, TransferAction action) {
super.handleInBackground(files, action);
log.log(Level.WARNING, getRootCauseMessage(e), e);
}
@Override
protected void load(List<File> files, TransferAction action) {
try {
if (files.size() > 1 || containsOnly(files, FILES)) {
files = Arrays.asList(files.get(0).getParentFile());
}
// make sure we have access to the parent folder structure, not just the dropped file
if (isMacSandbox()) {
MacAppUtilities.askUnlockFolders(getWindow(tree), files);
}
try {
// use fast file to minimize system calls like length(), isDirectory(), isFile(), ...
FastFile root = new FastFile(filter(files, FOLDERS).get(0));
TreeNode[] node = files.stream().map(FastFile::new).map(this::getTreeNode).toArray(TreeNode[]::new);
// publish on EDT
TreeNode[] node = { getTreeNode(root) };
publish(node);
} catch (InterruptedException e) {
} catch (CancellationException e) {
// supposed to happen if background execution was aborted
}
}
private TreeNode getTreeNode(File file) throws InterruptedException {
private TreeNode getTreeNode(File file) {
if (Thread.interrupted()) {
throw new InterruptedException();
throw new CancellationException();
}
if (file.isDirectory()) {

View File

@ -21,11 +21,11 @@ public class FilterPanel extends JComponent {
add(fileTreePanel, "grow 1, w 300:pref:500");
add(toolsPanel, "grow 2");
fileTreePanel.addPropertyChangeListener("filetree", evt -> {
fileTreePanel.addPropertyChangeListener(FileTreePanel.FILE_TREE_PROPERTY, evt -> {
// stopped loading, refresh tools
for (int i = 0; i < toolsPanel.getTabCount(); i++) {
Tool<?> tool = (Tool<?>) toolsPanel.getComponentAt(i);
tool.updateRoot(fileTreePanel.getFileTree().getRoot().getFile());
tool.setRoot(fileTreePanel.getFileTree().getRoot());
}
});
}

View File

@ -11,6 +11,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.CancellationException;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
@ -53,8 +54,8 @@ class MediaInfoTool extends Tool<TableModel> {
}
@Override
protected TableModel createModelInBackground(File root) {
if (root == null) {
protected TableModel createModelInBackground(List<File> root) {
if (root.isEmpty()) {
return new MediaInfoTableModel();
}
@ -74,9 +75,13 @@ class MediaInfoTool extends Tool<TableModel> {
});
});
} catch (IllegalArgumentException e) {
debug.finest(e.getMessage());
debug.finest(e::toString);
} catch (Exception e) {
debug.warning(e.getMessage());
debug.warning(e::toString);
}
if (Thread.interrupted()) {
throw new CancellationException();
}
});
}

View File

@ -7,13 +7,12 @@ import java.awt.Color;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
@ -51,14 +50,11 @@ class SplitTool extends Tool<TreeModel> {
tree.setTransferHandler(new DefaultTransferHandler(null, new FileTreeExportHandler()));
tree.setDragEnabled(true);
spinnerModel.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent evt) {
// update model in foreground, will be much faster than the initial load because length() is cached now
if (getRoot() != null) {
updateRoot(getRoot());
}
spinnerModel.addChangeListener(evt -> {
List<File> root = getRoot();
if (root.size() > 0) {
setRoot(root);
}
});
}
@ -68,8 +64,8 @@ class SplitTool extends Tool<TreeModel> {
}
@Override
protected TreeModel createModelInBackground(File root) throws InterruptedException {
if (root == null) {
protected TreeModel createModelInBackground(List<File> root) {
if (root.isEmpty()) {
return new DefaultTreeModel(new FolderNode("Volumes", emptyList()));
}
@ -93,7 +89,7 @@ class SplitTool extends Tool<TreeModel> {
if (totalSize + fileSize > splitSize) {
// part is full, add node and start with next one
rootGroup.add(createStatisticsNode(String.format("Disk %d", nextPart++), currentPart));
rootGroup.add(createStatisticsNode(nextPart++, currentPart));
// reset total size and file list
totalSize = 0;
@ -102,11 +98,15 @@ class SplitTool extends Tool<TreeModel> {
totalSize += fileSize;
currentPart.add(f);
if (Thread.interrupted()) {
throw new CancellationException();
}
}
if (!currentPart.isEmpty()) {
// add last part
rootGroup.add(createStatisticsNode(String.format("Disk %d", nextPart++), currentPart));
rootGroup.add(createStatisticsNode(nextPart++, currentPart));
}
if (!remainder.isEmpty()) {
@ -121,4 +121,9 @@ class SplitTool extends Tool<TreeModel> {
tree.setModel(model);
}
protected FolderNode createStatisticsNode(int disk, List<File> files) {
System.out.println(files);
return createStatisticsNode(String.format("Disk %,d", disk), files);
}
}

View File

@ -1,60 +1,66 @@
package net.filebot.ui.filter;
import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
import static net.filebot.Logging.*;
import static net.filebot.util.ExceptionUtilities.*;
import static net.filebot.util.FileUtilities.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import javax.swing.JComponent;
import javax.swing.SwingWorker;
import javax.swing.tree.TreeNode;
import org.apache.commons.io.FileUtils;
import net.filebot.ui.filter.FileTree.FileNode;
import net.filebot.ui.filter.FileTree.FolderNode;
import net.filebot.util.ExceptionUtilities;
import net.filebot.util.FileUtilities;
import net.filebot.util.ui.LoadingOverlayPane;
abstract class Tool<M> extends JComponent {
private UpdateModelTask updateTask = null;
private File root = null;
private List<File> root = emptyList();
private UpdateModelTask updateTask;
public Tool(String name) {
setName(name);
}
public File getRoot() {
public List<File> getRoot() {
return root;
}
public void updateRoot(File root) {
public void setRoot(List<File> root) {
this.root = root;
if (updateTask != null) {
updateTask.cancel(true);
}
Tool.this.firePropertyChange(LoadingOverlayPane.LOADING_PROPERTY, false, true);
setLoading(true);
updateTask = new UpdateModelTask(root);
updateTask.execute();
}
protected abstract M createModelInBackground(File root) throws InterruptedException;
protected void setLoading(boolean loading) {
firePropertyChange(LoadingOverlayPane.LOADING_PROPERTY, !loading, loading);
}
protected abstract M createModelInBackground(List<File> root) throws Exception;
protected abstract void setModel(M model);
private class UpdateModelTask extends SwingWorker<M, Void> {
private final File root;
private final List<File> root;
public UpdateModelTask(File root) {
public UpdateModelTask(List<File> root) {
this.root = root;
}
@ -66,7 +72,7 @@ abstract class Tool<M> extends JComponent {
@Override
protected void done() {
if (this == updateTask) {
Tool.this.firePropertyChange(LoadingOverlayPane.LOADING_PROPERTY, true, false);
setLoading(false);
}
// update task will only be cancelled if a newer update task has been started
@ -74,14 +80,12 @@ abstract class Tool<M> extends JComponent {
try {
setModel(get());
} catch (Exception e) {
Throwable cause = ExceptionUtilities.getRootCause(e);
Throwable cause = getRootCause(e);
if (cause instanceof ConcurrentModificationException || cause instanceof InterruptedException) {
// if it happens, it is supposed to
debug.log(Level.FINEST, e.getMessage(), e);
if (cause instanceof InterruptedException || cause instanceof CancellationException) {
debug.log(Level.FINEST, e, e::toString); // if it happens, it is supposed to
} else {
// should not happen
debug.log(Level.WARNING, e.getMessage(), e);
debug.log(Level.WARNING, e, e::toString); // should not happen
}
}
}
@ -89,26 +93,17 @@ abstract class Tool<M> extends JComponent {
}
protected List<TreeNode> createFileNodes(Collection<File> files) {
List<TreeNode> nodes = new ArrayList<TreeNode>(files.size());
for (File f : files) {
nodes.add(new FileNode(f));
}
return nodes;
return files.stream().map(FileNode::new).collect(toList());
}
protected FolderNode createStatisticsNode(String name, List<File> files) {
long totalCount = 0;
long totalSize = 0;
for (File f : files) {
totalCount += FileUtilities.listFiles(f, FileUtilities.FILES).size();
totalSize += FileUtils.sizeOf(f);
}
List<File> selection = listFiles(files, FILES, null);
long size = selection.stream().mapToLong(File::length).sum();
// set node text (e.g. txt (1 file, 42 Byte))
String title = String.format("%s (%,d %s, %s)", name, totalCount, totalCount == 1 ? "file" : "files", FileUtilities.formatSize(totalSize));
String title = String.format("%s (%,d %s, %s)", name, selection.size(), selection.size() == 1 ? "file" : "files", FileUtilities.formatSize(size));
return new FolderNode(null, title, createFileNodes(files));
return new FolderNode(title, createFileNodes(files));
}
}

View File

@ -10,6 +10,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CancellationException;
import java.util.SortedMap;
import java.util.TreeMap;
@ -44,12 +45,13 @@ class TypeTool extends Tool<TreeModel> {
}
@Override
protected TreeModel createModelInBackground(File root) throws InterruptedException {
if (root == null) {
protected TreeModel createModelInBackground(List<File> root) {
if (root.isEmpty()) {
return new DefaultTreeModel(new FolderNode("Types", emptyList()));
}
List<File> filesAndFolders = listFiles(root, NOT_HIDDEN, HUMAN_NAME_ORDER);
List<TreeNode> groups = new ArrayList<TreeNode>();
for (Entry<String, FileFilter> it : getMetaTypes().entrySet()) {
@ -57,15 +59,24 @@ class TypeTool extends Tool<TreeModel> {
if (selection.size() > 0) {
groups.add(createStatisticsNode(it.getKey(), selection));
}
if (Thread.interrupted()) {
throw new CancellationException();
}
}
SortedMap<String, TreeNode> extensionGroups = new TreeMap<String, TreeNode>(String.CASE_INSENSITIVE_ORDER);
for (Entry<String, List<File>> it : mapByExtension(filter(filesAndFolders, FILES)).entrySet()) {
if (it.getKey() == null)
continue;
for (Entry<String, List<File>> it : mapByExtension(filter(filesAndFolders, FILES)).entrySet()) {
if (it.getKey() != null) {
extensionGroups.put(it.getKey(), createStatisticsNode(it.getKey(), it.getValue()));
}
if (Thread.interrupted()) {
throw new CancellationException();
}
}
groups.addAll(extensionGroups.values());
// create tree model