* full support for multiple checksum types (SFV, MD5, SHA-1)
notes: * updated to MigLayout 3.6.3 * better exception handling in *TransferablePolicy * added checksum toggle button and artwork * poperly cancel computation tasks on reset * better "Total Progress" visibility behaviour * improved checksum table model classes, better update/repaint behaviour
This commit is contained in:
parent
9d7af8bd96
commit
87e8d830ce
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -26,7 +26,7 @@ import net.sourceforge.filebot.ui.panel.analyze.AnalyzePanel;
|
||||||
import net.sourceforge.filebot.ui.panel.episodelist.EpisodeListPanel;
|
import net.sourceforge.filebot.ui.panel.episodelist.EpisodeListPanel;
|
||||||
import net.sourceforge.filebot.ui.panel.list.ListPanel;
|
import net.sourceforge.filebot.ui.panel.list.ListPanel;
|
||||||
import net.sourceforge.filebot.ui.panel.rename.RenamePanel;
|
import net.sourceforge.filebot.ui.panel.rename.RenamePanel;
|
||||||
import net.sourceforge.filebot.ui.panel.sfv.SfvPanel;
|
import net.sourceforge.filebot.ui.panel.sfv.ChecksumPanel;
|
||||||
import net.sourceforge.filebot.ui.panel.subtitle.SubtitlePanel;
|
import net.sourceforge.filebot.ui.panel.subtitle.SubtitlePanel;
|
||||||
import net.sourceforge.tuned.MessageBus;
|
import net.sourceforge.tuned.MessageBus;
|
||||||
import net.sourceforge.tuned.MessageHandler;
|
import net.sourceforge.tuned.MessageHandler;
|
||||||
|
@ -85,7 +85,7 @@ public class FileBotWindow extends JFrame implements ListSelectionListener {
|
||||||
panels.add(new AnalyzePanel());
|
panels.add(new AnalyzePanel());
|
||||||
panels.add(new EpisodeListPanel());
|
panels.add(new EpisodeListPanel());
|
||||||
panels.add(new SubtitlePanel());
|
panels.add(new SubtitlePanel());
|
||||||
panels.add(new SfvPanel());
|
panels.add(new ChecksumPanel());
|
||||||
|
|
||||||
return panels;
|
return panels;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class FileTransferableMessageHandler implements MessageHandler {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(String topic, Object... messages) {
|
public void handle(String topic, Object... messages) throws Exception {
|
||||||
// switch to panel
|
// switch to panel
|
||||||
MessageBus.getDefault().publish("panel", panel);
|
MessageBus.getDefault().publish("panel", panel);
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<Abstra
|
||||||
protected void load(List<File> files) {
|
protected void load(List<File> files) {
|
||||||
try {
|
try {
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
|
// use fast file to minimize system calls like length(), isDirectory(), isFile(), ...
|
||||||
AbstractTreeNode node = getTreeNode(new FastFile(file.getPath()));
|
AbstractTreeNode node = getTreeNode(new FastFile(file.getPath()));
|
||||||
|
|
||||||
// publish on EDT
|
// publish on EDT
|
||||||
|
|
|
@ -11,9 +11,6 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileBotUtilities;
|
import net.sourceforge.filebot.FileBotUtilities;
|
||||||
import net.sourceforge.filebot.torrent.Torrent;
|
import net.sourceforge.filebot.torrent.Torrent;
|
||||||
import net.sourceforge.filebot.ui.FileBotList;
|
import net.sourceforge.filebot.ui.FileBotList;
|
||||||
|
@ -44,7 +41,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void load(List<File> files) {
|
protected void load(List<File> files) throws IOException {
|
||||||
// set title based on parent folder of first file
|
// set title based on parent folder of first file
|
||||||
list.setTitle(FileUtilities.getFolderName(files.get(0).getParentFile()));
|
list.setTitle(FileUtilities.getFolderName(files.get(0).getParentFile()));
|
||||||
|
|
||||||
|
@ -70,26 +67,21 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void loadTorrents(List<File> torrentFiles) {
|
private void loadTorrents(List<File> torrentFiles) throws IOException {
|
||||||
try {
|
List<Torrent> torrents = new ArrayList<Torrent>(torrentFiles.size());
|
||||||
List<Torrent> torrents = new ArrayList<Torrent>(torrentFiles.size());
|
|
||||||
|
for (File file : torrentFiles) {
|
||||||
for (File file : torrentFiles) {
|
torrents.add(new Torrent(file));
|
||||||
torrents.add(new Torrent(file));
|
}
|
||||||
|
|
||||||
|
if (torrentFiles.size() == 1) {
|
||||||
|
list.setTitle(FileUtilities.getNameWithoutExtension(torrents.get(0).getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Torrent torrent : torrents) {
|
||||||
|
for (Torrent.Entry entry : torrent.getFiles()) {
|
||||||
|
list.getModel().add(FileUtilities.getNameWithoutExtension(entry.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (torrentFiles.size() == 1) {
|
|
||||||
list.setTitle(FileUtilities.getNameWithoutExtension(torrents.get(0).getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Torrent torrent : torrents) {
|
|
||||||
for (Torrent.Entry entry : torrent.getFiles()) {
|
|
||||||
list.getModel().add(FileUtilities.getNameWithoutExtension(entry.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// should not happen
|
|
||||||
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import static net.sourceforge.tuned.FileUtilities.getNameWithoutExtension;
|
||||||
import java.awt.datatransfer.Transferable;
|
import java.awt.datatransfer.Transferable;
|
||||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -45,7 +46,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(Transferable tr) {
|
public boolean accept(Transferable tr) throws Exception {
|
||||||
return tr.isDataFlavorSupported(stringFlavor) || super.accept(tr);
|
return tr.isDataFlavorSupported(stringFlavor) || super.accept(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleTransferable(Transferable tr, TransferAction action) {
|
public void handleTransferable(Transferable tr, TransferAction action) throws Exception {
|
||||||
if (action == TransferAction.PUT) {
|
if (action == TransferAction.PUT) {
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
@ -121,7 +122,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void load(List<File> files) {
|
protected void load(List<File> files) throws FileNotFoundException {
|
||||||
if (containsOnly(files, LIST_FILES)) {
|
if (containsOnly(files, LIST_FILES)) {
|
||||||
loadListFiles(files);
|
loadListFiles(files);
|
||||||
} else if (containsOnly(files, TORRENT_FILES)) {
|
} else if (containsOnly(files, TORRENT_FILES)) {
|
||||||
|
@ -144,28 +145,24 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void loadListFiles(List<File> files) {
|
protected void loadListFiles(List<File> files) throws FileNotFoundException {
|
||||||
try {
|
List<MutableString> entries = new ArrayList<MutableString>();
|
||||||
List<MutableString> entries = new ArrayList<MutableString>();
|
|
||||||
|
for (File file : files) {
|
||||||
|
Scanner scanner = new Scanner(file, "UTF-8").useDelimiter(LINE_SEPARATOR);
|
||||||
|
|
||||||
for (File file : files) {
|
while (scanner.hasNext()) {
|
||||||
Scanner scanner = new Scanner(file, "UTF-8").useDelimiter(LINE_SEPARATOR);
|
String line = scanner.next();
|
||||||
|
|
||||||
while (scanner.hasNext()) {
|
if (line.trim().length() > 0) {
|
||||||
String line = scanner.next();
|
entries.add(new MutableString(line));
|
||||||
|
|
||||||
if (line.trim().length() > 0) {
|
|
||||||
entries.add(new MutableString(line));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scanner.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
submit(entries);
|
scanner.close();
|
||||||
} catch (IOException e) {
|
|
||||||
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
submit(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.awt.Color.WHITE;
|
||||||
|
import static java.awt.Cursor.*;
|
||||||
|
import static java.awt.Font.DIALOG;
|
||||||
|
import static java.awt.Font.PLAIN;
|
||||||
|
import static java.awt.RenderingHints.KEY_RENDERING;
|
||||||
|
import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING;
|
||||||
|
import static java.awt.RenderingHints.VALUE_RENDER_QUALITY;
|
||||||
|
import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
import static java.lang.Math.round;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
|
||||||
|
import javax.swing.Action;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JToggleButton;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
|
|
||||||
|
|
||||||
|
public class ChecksumButton extends JToggleButton {
|
||||||
|
|
||||||
|
private static final ImageIcon contentArea = ResourceManager.getIcon("button.checksum");
|
||||||
|
private static final ImageIcon contentAreaSelected = ResourceManager.getIcon("button.checksum.selected");
|
||||||
|
|
||||||
|
|
||||||
|
public ChecksumButton(Action action) {
|
||||||
|
super(action);
|
||||||
|
|
||||||
|
setPreferredSize(new Dimension(max(contentAreaSelected.getIconWidth(), contentArea.getIconWidth()), max(contentAreaSelected.getIconHeight(), contentArea.getIconHeight())));
|
||||||
|
setMinimumSize(getPreferredSize());
|
||||||
|
setMaximumSize(getPreferredSize());
|
||||||
|
|
||||||
|
setForeground(WHITE);
|
||||||
|
setFont(new Font(DIALOG, PLAIN, 11));
|
||||||
|
|
||||||
|
setContentAreaFilled(false);
|
||||||
|
setFocusPainted(false);
|
||||||
|
|
||||||
|
setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
super.setEnabled(enabled);
|
||||||
|
|
||||||
|
// set appropriate cursor
|
||||||
|
setCursor(getPredefinedCursor(enabled ? HAND_CURSOR : DEFAULT_CURSOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
|
g2d.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
g2d.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
|
||||||
|
|
||||||
|
// paint background image in the center
|
||||||
|
if (isSelected()) {
|
||||||
|
contentAreaSelected.paintIcon(this, g2d, (int) round((getWidth() - contentAreaSelected.getIconWidth()) / (double) 2), (int) round((getHeight() - contentAreaSelected.getIconHeight()) / (double) 2));
|
||||||
|
} else {
|
||||||
|
contentArea.paintIcon(this, g2d, (int) round((getWidth() - contentArea.getIconWidth()) / (double) 2), (int) round((getHeight() - contentArea.getIconHeight()) / (double) 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle2D textBounds = g2d.getFontMetrics().getStringBounds(getText(), g2d);
|
||||||
|
|
||||||
|
// draw text in the center
|
||||||
|
g2d.drawString(getText(), round((getWidth() - textBounds.getWidth()) / 2) + 1, round(getHeight() / 2 - textBounds.getY() - textBounds.getHeight() / 2));
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,15 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.beans.PropertyChangeSupport;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
|
||||||
|
import javax.swing.SwingWorker.StateValue;
|
||||||
|
import javax.swing.event.SwingPropertyChangeSupport;
|
||||||
|
|
||||||
import net.sourceforge.tuned.ExceptionUtilities;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
|
||||||
|
|
||||||
|
|
||||||
class ChecksumCell {
|
class ChecksumCell {
|
||||||
|
@ -37,33 +40,14 @@ class ChecksumCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ChecksumCell(String name, File root, ChecksumComputationTask computationTask) {
|
public ChecksumCell(String name, File root, ChecksumComputationTask task) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.task = computationTask;
|
this.hashes = new EnumMap<HashType, String>(HashType.class);
|
||||||
|
this.task = task;
|
||||||
|
|
||||||
// forward property change events
|
// forward property change events
|
||||||
task.addPropertyChangeListener(new SwingWorkerPropertyChangeAdapter() {
|
task.addPropertyChangeListener(taskListener);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
|
||||||
super.propertyChange(evt);
|
|
||||||
|
|
||||||
pcs.firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done(PropertyChangeEvent evt) {
|
|
||||||
try {
|
|
||||||
hashes = task.get();
|
|
||||||
} catch (Exception e) {
|
|
||||||
error = ExceptionUtilities.getRootCause(e);
|
|
||||||
} finally {
|
|
||||||
task = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,11 +61,25 @@ class ChecksumCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getChecksum(HashType type) {
|
public String getChecksum(HashType hash) {
|
||||||
if (hashes != null)
|
return hashes.get(hash);
|
||||||
return hashes.get(type);
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void putTask(ChecksumComputationTask computationTask) {
|
||||||
|
if (task != null) {
|
||||||
|
task.removePropertyChangeListener(taskListener);
|
||||||
|
task.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
task = computationTask;
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
// forward property change events
|
||||||
|
task.addPropertyChangeListener(taskListener);
|
||||||
|
|
||||||
|
// state changed to PENDING
|
||||||
|
pcs.firePropertyChange("state", null, getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,27 +94,31 @@ class ChecksumCell {
|
||||||
|
|
||||||
|
|
||||||
public State getState() {
|
public State getState() {
|
||||||
if (hashes != null)
|
if (task != null) {
|
||||||
return State.READY;
|
switch (task.getState()) {
|
||||||
if (error != null)
|
case PENDING:
|
||||||
return State.ERROR;
|
return State.PENDING;
|
||||||
|
default:
|
||||||
switch (task.getState()) {
|
return State.PROGRESS;
|
||||||
case PENDING:
|
}
|
||||||
return State.PENDING;
|
|
||||||
default:
|
|
||||||
return State.PROGRESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
return State.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return State.READY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
// clear property change support first
|
// clear property change support
|
||||||
for (PropertyChangeListener listener : pcs.getPropertyChangeListeners()) {
|
for (PropertyChangeListener listener : pcs.getPropertyChangeListeners()) {
|
||||||
pcs.removePropertyChangeListener(listener);
|
pcs.removePropertyChangeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
|
task.removePropertyChangeListener(taskListener);
|
||||||
task.cancel(true);
|
task.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +134,42 @@ class ChecksumCell {
|
||||||
return String.format("%s %s", name, hashes);
|
return String.format("%s %s", name, hashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
|
private final PropertyChangeListener taskListener = new PropertyChangeListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
if ("state".equals(evt.getPropertyName())) {
|
||||||
|
if (evt.getNewValue() == StateValue.DONE)
|
||||||
|
done(evt);
|
||||||
|
|
||||||
|
// cell state changed because worker state changed
|
||||||
|
pcs.firePropertyChange("state", null, getState());
|
||||||
|
} else {
|
||||||
|
// progress events
|
||||||
|
pcs.firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void done(PropertyChangeEvent evt) {
|
||||||
|
try {
|
||||||
|
hashes.putAll(task.get());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Throwable cause = ExceptionUtilities.getRootCause(e);
|
||||||
|
|
||||||
|
// ignore cancellation
|
||||||
|
if (cause instanceof CancellationException) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = cause;
|
||||||
|
} finally {
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true);
|
||||||
|
|
||||||
|
|
||||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
|
import javax.swing.SwingWorker.StateValue;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
public class ChecksumCellRenderer extends DefaultTableCellRenderer {
|
||||||
|
|
||||||
|
private final SwingWorkerCellRenderer progressRenderer = new SwingWorkerCellRenderer();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
boolean pendingWorker = false;
|
||||||
|
|
||||||
|
if (value instanceof SwingWorker) {
|
||||||
|
if (((SwingWorker<?, ?>) value).getState() != StateValue.PENDING)
|
||||||
|
return progressRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||||
|
|
||||||
|
pendingWorker = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore focus
|
||||||
|
super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
|
||||||
|
|
||||||
|
// restore text color
|
||||||
|
setForeground(isSelected ? table.getSelectionForeground() : table.getForeground());
|
||||||
|
setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
|
||||||
|
|
||||||
|
if (pendingWorker) {
|
||||||
|
setText("Pending...");
|
||||||
|
} else if (value == null && !isSelected) {
|
||||||
|
setBackground(derive(table.getGridColor(), 0.1f));
|
||||||
|
} else if (value instanceof Throwable) {
|
||||||
|
setText(ExceptionUtilities.getRootCauseMessage((Throwable) value));
|
||||||
|
|
||||||
|
if (!isSelected) {
|
||||||
|
setForeground(Color.RED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Color derive(Color color, float alpha) {
|
||||||
|
return new Color(((((int) (alpha * 255)) & 0xFF) << 24) & (color.getRGB() | 0xFF000000), true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,10 @@ import static java.lang.Math.max;
|
||||||
|
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.beans.PropertyChangeSupport;
|
import java.beans.PropertyChangeSupport;
|
||||||
import java.util.ArrayList;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -22,7 +23,7 @@ class ChecksumComputationService {
|
||||||
|
|
||||||
public static final String TASK_COUNT_PROPERTY = "taskCount";
|
public static final String TASK_COUNT_PROPERTY = "taskCount";
|
||||||
|
|
||||||
private final List<ThreadPoolExecutor> executors = new ArrayList<ThreadPoolExecutor>();
|
private final Set<ThreadPoolExecutor> executors = new HashSet<ThreadPoolExecutor>(4);
|
||||||
|
|
||||||
private final AtomicInteger completedTaskCount = new AtomicInteger(0);
|
private final AtomicInteger completedTaskCount = new AtomicInteger(0);
|
||||||
private final AtomicInteger totalTaskCount = new AtomicInteger(0);
|
private final AtomicInteger totalTaskCount = new AtomicInteger(0);
|
||||||
|
@ -36,7 +37,12 @@ class ChecksumComputationService {
|
||||||
public void reset() {
|
public void reset() {
|
||||||
synchronized (executors) {
|
synchronized (executors) {
|
||||||
for (ExecutorService executor : executors) {
|
for (ExecutorService executor : executors) {
|
||||||
executor.shutdownNow();
|
for (Runnable runnable : executor.shutdownNow()) {
|
||||||
|
// cancel all remaining tasks
|
||||||
|
if (runnable instanceof Future) {
|
||||||
|
((Future<?>) runnable).cancel(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
totalTaskCount.set(0);
|
totalTaskCount.set(0);
|
||||||
|
@ -50,7 +56,7 @@ class ChecksumComputationService {
|
||||||
|
|
||||||
|
|
||||||
public int getTaskCount() {
|
public int getTaskCount() {
|
||||||
return getTotalTaskCount() - getCompletedTaskCount();
|
return totalTaskCount.get() - completedTaskCount.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,7 +85,10 @@ class ChecksumComputationService {
|
||||||
super(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new DefaultThreadFactory("ChecksumComputationPool", Thread.MIN_PRIORITY));
|
super(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new DefaultThreadFactory("ChecksumComputationPool", Thread.MIN_PRIORITY));
|
||||||
|
|
||||||
synchronized (executors) {
|
synchronized (executors) {
|
||||||
executors.add(this);
|
if (executors.add(this) && executors.size() == 1) {
|
||||||
|
totalTaskCount.set(0);
|
||||||
|
completedTaskCount.set(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prestartAllCoreThreads();
|
prestartAllCoreThreads();
|
||||||
|
@ -133,8 +142,22 @@ class ChecksumComputationService {
|
||||||
protected void afterExecute(Runnable r, Throwable t) {
|
protected void afterExecute(Runnable r, Throwable t) {
|
||||||
super.afterExecute(r, t);
|
super.afterExecute(r, t);
|
||||||
|
|
||||||
completedTaskCount.incrementAndGet();
|
if (isValid()) {
|
||||||
fireTaskCountChanged();
|
if (r instanceof Future && ((Future<?>) r).isCancelled()) {
|
||||||
|
totalTaskCount.decrementAndGet();
|
||||||
|
} else {
|
||||||
|
completedTaskCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
fireTaskCountChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected boolean isValid() {
|
||||||
|
synchronized (executors) {
|
||||||
|
return executors.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,12 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,48 +18,52 @@ class ChecksumComputationTask extends SwingWorker<Map<HashType, String>, Void> {
|
||||||
private static final int BUFFER_SIZE = 32 * 1024;
|
private static final int BUFFER_SIZE = 32 * 1024;
|
||||||
|
|
||||||
private final File file;
|
private final File file;
|
||||||
private final HashType type;
|
private final HashType hashType;
|
||||||
|
|
||||||
|
|
||||||
public ChecksumComputationTask(File file, HashType type) {
|
public ChecksumComputationTask(File file, HashType hashType) {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.type = type;
|
this.hashType = hashType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<HashType, String> doInBackground() throws Exception {
|
protected Map<HashType, String> doInBackground() throws Exception {
|
||||||
Hash hash = type.newInstance();
|
if (!file.exists())
|
||||||
|
throw new FileNotFoundException("File not found");
|
||||||
|
|
||||||
|
// create hash instance
|
||||||
|
Hash hash = hashType.newHash();
|
||||||
|
|
||||||
|
// cache length for speed
|
||||||
long length = file.length();
|
long length = file.length();
|
||||||
|
|
||||||
if (length > 0) {
|
// open file
|
||||||
InputStream in = new FileInputStream(file);
|
InputStream in = new FileInputStream(file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
|
||||||
try {
|
long position = 0;
|
||||||
byte[] buffer = new byte[BUFFER_SIZE];
|
int len = 0;
|
||||||
|
|
||||||
|
while ((len = in.read(buffer)) >= 0) {
|
||||||
|
position += len;
|
||||||
|
|
||||||
long position = 0;
|
hash.update(buffer, 0, len);
|
||||||
int len = 0;
|
|
||||||
|
|
||||||
while ((len = in.read(buffer)) >= 0) {
|
// update progress
|
||||||
position += len;
|
setProgress((int) ((position * 100) / length));
|
||||||
|
|
||||||
hash.update(buffer, 0, len);
|
// check abort status
|
||||||
|
if (isCancelled() || Thread.interrupted()) {
|
||||||
// update progress
|
throw new CancellationException();
|
||||||
setProgress((int) ((position * 100) / length));
|
|
||||||
|
|
||||||
// check abort status
|
|
||||||
if (isCancelled()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
in.close();
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.singletonMap(type, hash.digest());
|
return Collections.singletonMap(hashType, hash.digest());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,293 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.filebot.ui.panel.sfv.ChecksumTableModel.*;
|
||||||
|
import static net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.ButtonGroup;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JToggleButton;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.border.TitledBorder;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
|
import net.sourceforge.filebot.ui.FileBotPanel;
|
||||||
|
import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
|
||||||
|
import net.sourceforge.filebot.ui.SelectDialog;
|
||||||
|
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
||||||
|
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
||||||
|
import net.sourceforge.filebot.ui.transfer.SaveAction;
|
||||||
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
import net.sourceforge.tuned.MessageHandler;
|
||||||
|
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
public class ChecksumPanel extends FileBotPanel {
|
||||||
|
|
||||||
|
private final ChecksumComputationService computationService = new ChecksumComputationService();
|
||||||
|
|
||||||
|
private final ChecksumTable table = new ChecksumTable();
|
||||||
|
|
||||||
|
private final ChecksumTableTransferablePolicy transferablePolicy = new ChecksumTableTransferablePolicy(table.getModel(), computationService);
|
||||||
|
private final ChecksumTableExportHandler exportHandler = new ChecksumTableExportHandler(table.getModel());
|
||||||
|
|
||||||
|
private final MessageHandler messageHandler = new FileTransferableMessageHandler(this, transferablePolicy);
|
||||||
|
|
||||||
|
|
||||||
|
public ChecksumPanel() {
|
||||||
|
super("SFV", ResourceManager.getIcon("panel.sfv"));
|
||||||
|
|
||||||
|
table.setTransferHandler(new DefaultTransferHandler(transferablePolicy, exportHandler));
|
||||||
|
|
||||||
|
JPanel contentPane = new JPanel(new MigLayout("insets 0, fill", "", "[fill]10px[nogrid, bottom]4px"));
|
||||||
|
contentPane.setBorder(new TitledBorder(getPanelName()));
|
||||||
|
|
||||||
|
setLayout(new MigLayout("insets dialog, fill"));
|
||||||
|
add(contentPane, "grow");
|
||||||
|
|
||||||
|
contentPane.add(new JScrollPane(table), "grow, wrap");
|
||||||
|
|
||||||
|
contentPane.add(new JButton(loadAction), "gap left 15px");
|
||||||
|
contentPane.add(new JButton(saveAction));
|
||||||
|
contentPane.add(new JButton(clearAction), "gap right 40px");
|
||||||
|
|
||||||
|
// hash function toggle button group
|
||||||
|
ButtonGroup group = new ButtonGroup();
|
||||||
|
|
||||||
|
for (HashType hash : HashType.values()) {
|
||||||
|
JToggleButton button = new ChecksumButton(new ChangeHashTypeAction(hash));
|
||||||
|
|
||||||
|
group.add(button);
|
||||||
|
contentPane.add(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentPane.add(new TotalProgressPanel(computationService), "gap left 35px:push, gap right 7px, hidemode 1");
|
||||||
|
|
||||||
|
// cancel and restart computations whenever the hash function has been changed
|
||||||
|
table.getModel().addPropertyChangeListener(new PropertyChangeListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
if (HASH_TYPE_PROPERTY.equals(evt.getPropertyName())) {
|
||||||
|
restartComputation((HashType) evt.getNewValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shortcut DELETE
|
||||||
|
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void restartComputation(HashType hash) {
|
||||||
|
// cancel all running computations
|
||||||
|
computationService.reset();
|
||||||
|
|
||||||
|
ChecksumTableModel model = table.getModel();
|
||||||
|
|
||||||
|
// calculate new hashes, one executor for each checksum column
|
||||||
|
Map<File, ExecutorService> executors = new HashMap<File, ExecutorService>(4);
|
||||||
|
|
||||||
|
for (ChecksumRow row : model.rows()) {
|
||||||
|
for (ChecksumCell cell : row.values()) {
|
||||||
|
if (cell.getChecksum(hash) == null && cell.getRoot().isDirectory()) {
|
||||||
|
cell.putTask(new ChecksumComputationTask(new File(cell.getRoot(), cell.getName()), hash));
|
||||||
|
|
||||||
|
ExecutorService executor = executors.get(cell.getRoot());
|
||||||
|
|
||||||
|
if (executor == null) {
|
||||||
|
executor = computationService.newExecutor();
|
||||||
|
executors.put(cell.getRoot(), executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// start computation
|
||||||
|
executor.execute(cell.getTask());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start shutdown sequence for all created executors
|
||||||
|
for (ExecutorService executor : executors.values()) {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageHandler getMessageHandler() {
|
||||||
|
return messageHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SaveAction saveAction = new ChecksumTableSaveAction();
|
||||||
|
|
||||||
|
private final LoadAction loadAction = new LoadAction(transferablePolicy);
|
||||||
|
|
||||||
|
private final AbstractAction clearAction = new AbstractAction("Clear", ResourceManager.getIcon("action.clear")) {
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
transferablePolicy.reset();
|
||||||
|
computationService.reset();
|
||||||
|
|
||||||
|
table.getModel().clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final AbstractAction removeAction = new AbstractAction("Remove") {
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (table.getSelectedRowCount() < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int firstSelectedRow = table.getSelectedRow();
|
||||||
|
|
||||||
|
// remove selected rows
|
||||||
|
table.getModel().remove(table.getSelectedRows());
|
||||||
|
|
||||||
|
// update computation service task count
|
||||||
|
computationService.purge();
|
||||||
|
|
||||||
|
// auto select next row
|
||||||
|
firstSelectedRow = Math.min(firstSelectedRow, table.getRowCount() - 1);
|
||||||
|
|
||||||
|
table.getSelectionModel().setSelectionInterval(firstSelectedRow, firstSelectedRow);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
protected class ChangeHashTypeAction extends AbstractAction implements PropertyChangeListener {
|
||||||
|
|
||||||
|
private ChangeHashTypeAction(HashType hash) {
|
||||||
|
super(hash.toString());
|
||||||
|
putValue(HASH_TYPE_PROPERTY, hash);
|
||||||
|
|
||||||
|
// initialize selected state
|
||||||
|
propertyChange(new PropertyChangeEvent(this, HASH_TYPE_PROPERTY, null, table.getModel().getHashType()));
|
||||||
|
|
||||||
|
transferablePolicy.addPropertyChangeListener(this);
|
||||||
|
table.getModel().addPropertyChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
table.getModel().setHashType((HashType) getValue(HASH_TYPE_PROPERTY));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
if (LOADING_PROPERTY.equals(evt.getPropertyName())) {
|
||||||
|
// update enabled state
|
||||||
|
setEnabled(!(Boolean) evt.getNewValue());
|
||||||
|
} else if (HASH_TYPE_PROPERTY.equals(evt.getPropertyName())) {
|
||||||
|
// update selected state
|
||||||
|
putValue(SELECTED_KEY, evt.getNewValue() == getValue(HASH_TYPE_PROPERTY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected class ChecksumTableSaveAction extends SaveAction {
|
||||||
|
|
||||||
|
private File selectedColumn = null;
|
||||||
|
|
||||||
|
|
||||||
|
public ChecksumTableSaveAction() {
|
||||||
|
super(exportHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChecksumTableExportHandler getExportHandler() {
|
||||||
|
return (ChecksumTableExportHandler) super.getExportHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canExport() {
|
||||||
|
return selectedColumn != null && super.canExport();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void export(File file) throws IOException {
|
||||||
|
getExportHandler().export(file, selectedColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDefaultFileName() {
|
||||||
|
return getExportHandler().getDefaultFileName(selectedColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected File getDefaultFolder() {
|
||||||
|
// use the column root as default folder in the file dialog
|
||||||
|
return selectedColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
List<File> options = new ArrayList<File>();
|
||||||
|
|
||||||
|
// filter out verification file columns
|
||||||
|
for (File file : table.getModel().checksumColumns()) {
|
||||||
|
if (file.isDirectory())
|
||||||
|
options.add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// can't export anything
|
||||||
|
if (options.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (options.size() == 1) {
|
||||||
|
// auto-select option if there is only one option
|
||||||
|
this.selectedColumn = options.get(0);
|
||||||
|
} else if (options.size() > 1) {
|
||||||
|
// user must select one option
|
||||||
|
SelectDialog<File> selectDialog = new SelectDialog<File>(SwingUtilities.getWindowAncestor(ChecksumPanel.this), options) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String convertValueToString(Object value) {
|
||||||
|
return FileUtilities.getFolderName((File) value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
selectDialog.getHeaderLabel().setText("Select checksum column:");
|
||||||
|
selectDialog.setVisible(true);
|
||||||
|
|
||||||
|
this.selectedColumn = selectDialog.getSelectedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedColumn != null) {
|
||||||
|
// continue if a column was selected
|
||||||
|
super.actionPerformed(e);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// reset selected column
|
||||||
|
this.selectedColumn = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,6 +13,8 @@ import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.swing.event.SwingPropertyChangeSupport;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileBotUtilities;
|
import net.sourceforge.filebot.FileBotUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,38 +55,52 @@ class ChecksumRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void setState(State newValue) {
|
||||||
|
State oldValue = this.state;
|
||||||
|
this.state = newValue;
|
||||||
|
|
||||||
|
pcs.firePropertyChange("state", oldValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public ChecksumCell getChecksum(File root) {
|
public ChecksumCell getChecksum(File root) {
|
||||||
return hashes.get(root);
|
return hashes.get(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void put(ChecksumCell cell) {
|
public Collection<ChecksumCell> values() {
|
||||||
ChecksumCell old = hashes.put(cell.getRoot(), cell);
|
return Collections.unmodifiableCollection(hashes.values());
|
||||||
|
|
||||||
// dispose of old map entry
|
|
||||||
if (old != null) {
|
|
||||||
old.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// update state immediately
|
|
||||||
updateState();
|
|
||||||
|
|
||||||
// keep state up-to-date
|
|
||||||
cell.addPropertyChangeListener(updateStateListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void updateState() {
|
public ChecksumCell put(ChecksumCell cell) {
|
||||||
|
ChecksumCell old = hashes.put(cell.getRoot(), cell);
|
||||||
|
|
||||||
|
// update state immediately, don't fire property change
|
||||||
state = getState(hashes.values());
|
state = getState(hashes.values());
|
||||||
|
|
||||||
|
// keep state up-to-date
|
||||||
|
cell.addPropertyChangeListener(updateStateListener);
|
||||||
|
|
||||||
|
return old;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
// clear property change support
|
||||||
|
for (PropertyChangeListener listener : pcs.getPropertyChangeListeners()) {
|
||||||
|
pcs.removePropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
for (ChecksumCell cell : hashes.values()) {
|
for (ChecksumCell cell : hashes.values()) {
|
||||||
cell.dispose();
|
cell.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
hashes.clear();
|
name = null;
|
||||||
|
embeddedChecksum = null;
|
||||||
|
hashes = null;
|
||||||
|
state = null;
|
||||||
|
pcs = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,7 +127,7 @@ class ChecksumRow {
|
||||||
String checksum = cell.getChecksum(type);
|
String checksum = cell.getChecksum(type);
|
||||||
|
|
||||||
if (checksum != null) {
|
if (checksum != null) {
|
||||||
checksumSet.add(checksum);
|
checksumSet.add(checksum.toLowerCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,8 +169,22 @@ class ChecksumRow {
|
||||||
private final PropertyChangeListener updateStateListener = new PropertyChangeListener() {
|
private final PropertyChangeListener updateStateListener = new PropertyChangeListener() {
|
||||||
|
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
updateState();
|
if ("state".equals(evt.getPropertyName())) {
|
||||||
|
setState(getState(hashes.values()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true);
|
||||||
|
|
||||||
|
|
||||||
|
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
pcs.addPropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
pcs.removePropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,17 @@
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.FileBotUtilities;
|
||||||
import net.sourceforge.tuned.ui.TunedUtilities.DragDropRowTableUI;
|
import net.sourceforge.tuned.ui.TunedUtilities.DragDropRowTableUI;
|
||||||
import javax.swing.JTable;
|
import javax.swing.JTable;
|
||||||
import javax.swing.ListSelectionModel;
|
import javax.swing.ListSelectionModel;
|
||||||
import javax.swing.table.TableColumn;
|
import javax.swing.table.TableColumn;
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileBotUtilities;
|
|
||||||
|
|
||||||
|
class ChecksumTable extends JTable {
|
||||||
class SfvTable extends JTable {
|
|
||||||
|
|
||||||
public SfvTable() {
|
public ChecksumTable() {
|
||||||
setFillsViewportHeight(true);
|
setFillsViewportHeight(true);
|
||||||
setAutoCreateRowSorter(true);
|
setAutoCreateRowSorter(true);
|
||||||
setAutoCreateColumnsFromModel(true);
|
setAutoCreateColumnsFromModel(true);
|
||||||
|
@ -23,12 +22,13 @@ class SfvTable extends JTable {
|
||||||
|
|
||||||
setRowHeight(20);
|
setRowHeight(20);
|
||||||
|
|
||||||
|
setDragEnabled(true);
|
||||||
setUI(new DragDropRowTableUI());
|
setUI(new DragDropRowTableUI());
|
||||||
|
|
||||||
// highlight CRC32 patterns in filenames in green and with smaller font-size
|
// highlight CRC32 patterns in filenames in green and with smaller font-size
|
||||||
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(FileBotUtilities.EMBEDDED_CHECKSUM_PATTERN, "#009900", "smaller"));
|
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(FileBotUtilities.EMBEDDED_CHECKSUM_PATTERN, "#009900", "smaller"));
|
||||||
setDefaultRenderer(ChecksumRow.State.class, new StateIconTableCellRenderer());
|
setDefaultRenderer(ChecksumRow.State.class, new StateIconCellRenderer());
|
||||||
setDefaultRenderer(ChecksumCell.class, new ChecksumTableCellRenderer());
|
setDefaultRenderer(ChecksumCell.class, new ChecksumCellRenderer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,115 +0,0 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
|
||||||
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Component;
|
|
||||||
import java.awt.Rectangle;
|
|
||||||
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JProgressBar;
|
|
||||||
import javax.swing.JTable;
|
|
||||||
import javax.swing.border.EmptyBorder;
|
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
|
||||||
import javax.swing.table.TableCellRenderer;
|
|
||||||
|
|
||||||
import net.sourceforge.tuned.ExceptionUtilities;
|
|
||||||
|
|
||||||
|
|
||||||
class ChecksumTableCellRenderer extends DefaultTableCellRenderer {
|
|
||||||
|
|
||||||
private final ProgressBarTableCellRenderer progressBarRenderer = new ProgressBarTableCellRenderer();
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
|
||||||
super.getTableCellRendererComponent(table, null, isSelected, false, row, column);
|
|
||||||
|
|
||||||
if (value instanceof ChecksumCell) {
|
|
||||||
ChecksumCell checksum = (ChecksumCell) value;
|
|
||||||
|
|
||||||
switch (checksum.getState()) {
|
|
||||||
case READY:
|
|
||||||
setText(checksum.getChecksum(HashType.CRC32));
|
|
||||||
break;
|
|
||||||
case PENDING:
|
|
||||||
setText("Pending ...");
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
setText(ExceptionUtilities.getMessage(checksum.getError()));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return progressBarRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static class ProgressBarTableCellRenderer extends JPanel implements TableCellRenderer {
|
|
||||||
|
|
||||||
private final JProgressBar progressBar = new JProgressBar(0, 100);
|
|
||||||
|
|
||||||
|
|
||||||
public ProgressBarTableCellRenderer() {
|
|
||||||
progressBar.setStringPainted(true);
|
|
||||||
|
|
||||||
setLayout(new BorderLayout());
|
|
||||||
add(progressBar, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
setBorder(new EmptyBorder(2, 2, 2, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
|
||||||
|
|
||||||
ChecksumComputationTask task = ((ChecksumCell) value).getTask();
|
|
||||||
|
|
||||||
if (task != null) {
|
|
||||||
progressBar.setValue(task.getProgress());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSelected) {
|
|
||||||
this.setBackground(table.getSelectionBackground());
|
|
||||||
} else {
|
|
||||||
this.setBackground(table.getBackground());
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridden for performance reasons.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void repaint(long tm, int x, int y, int width, int height) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridden for performance reasons.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void repaint(Rectangle r) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridden for performance reasons.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void repaint() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridden for performance reasons.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,19 +24,30 @@ class ChecksumTableExportHandler extends TextFileExportHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canExport() {
|
public boolean canExport() {
|
||||||
return model.getRowCount() > 0 && model.getChecksumColumns().size() > 0;
|
return model.getRowCount() > 0 && defaultColumn() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void export(Formatter out) {
|
public void export(Formatter out) {
|
||||||
export(out, model.getChecksumColumns().get(0));
|
export(out, defaultColumn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDefaultFileName() {
|
public String getDefaultFileName() {
|
||||||
return getDefaultFileName(model.getChecksumColumns().get(0));
|
return getDefaultFileName(defaultColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected File defaultColumn() {
|
||||||
|
// select first column that is not a verification file column
|
||||||
|
for (File root : model.checksumColumns()) {
|
||||||
|
if (root.isDirectory())
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,27 +63,38 @@ class ChecksumTableExportHandler extends TextFileExportHandler {
|
||||||
|
|
||||||
|
|
||||||
public void export(Formatter out, File column) {
|
public void export(Formatter out, File column) {
|
||||||
out.format("; Generated by %s on %tF at %<tT%n", Settings.getApplicationName(), new Date());
|
HashType hashType = model.getHashType();
|
||||||
|
|
||||||
|
// print header
|
||||||
|
out.format("; Generated by %s %s on %tF at %<tT%n", Settings.getApplicationName(), Settings.getApplicationVersion(), new Date());
|
||||||
out.format(";%n");
|
out.format(";%n");
|
||||||
out.format(";%n");
|
out.format(";%n");
|
||||||
|
|
||||||
for (ChecksumRow row : model) {
|
// print data
|
||||||
//TODO select hash type
|
VerificationFilePrinter printer = hashType.newPrinter(out);
|
||||||
out.format("%s %s%n", row.getName(), row.getChecksum(column).getChecksum(HashType.CRC32));
|
|
||||||
|
for (ChecksumRow row : model.rows()) {
|
||||||
|
ChecksumCell cell = row.getChecksum(column);
|
||||||
|
|
||||||
|
if (cell != null) {
|
||||||
|
String hash = cell.getChecksum(hashType);
|
||||||
|
|
||||||
|
if (hash != null) {
|
||||||
|
printer.println(cell.getName(), hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getDefaultFileName(File column) {
|
public String getDefaultFileName(File column) {
|
||||||
String name = "";
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
if (column != null)
|
if (column != null)
|
||||||
name = FileUtilities.getName(column);
|
sb.append(FileUtilities.getName(column));
|
||||||
|
else
|
||||||
|
sb.append("name");
|
||||||
|
|
||||||
if (name.isEmpty())
|
return sb.append(".").append(model.getHashType().getExtension()).toString();
|
||||||
name = "name";
|
|
||||||
|
|
||||||
return name + ".sfv";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,29 +2,25 @@
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
import static javax.swing.event.TableModelEvent.UPDATE;
|
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.beans.PropertyChangeSupport;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.AbstractList;
|
import java.util.AbstractList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.swing.event.TableModelEvent;
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import javax.swing.table.TableModel;
|
|
||||||
|
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
|
||||||
|
|
||||||
class ChecksumTableModel extends AbstractTableModel implements Iterable<ChecksumRow> {
|
class ChecksumTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
private final IndexedMap<String, ChecksumRow> rows = new IndexedMap<String, ChecksumRow>() {
|
private final IndexedMap<String, ChecksumRow> rows = new IndexedMap<String, ChecksumRow>() {
|
||||||
|
|
||||||
|
@ -34,7 +30,10 @@ class ChecksumTableModel extends AbstractTableModel implements Iterable<Checksum
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final List<File> columns = new ArrayList<File>(4);
|
private final List<File> checksumColumns = new ArrayList<File>(4);
|
||||||
|
|
||||||
|
public static final String HASH_TYPE_PROPERTY = "hashType";
|
||||||
|
private HashType hashType = HashType.SFV;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,19 +63,47 @@ class ChecksumTableModel extends AbstractTableModel implements Iterable<Checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public File getColumnRoot(int columnIndex) {
|
protected int getColumnIndex(ChecksumCell cell) {
|
||||||
return columns.get(columnIndex - 2);
|
int index = checksumColumns.indexOf(cell.getRoot());
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// add checksum column offset
|
||||||
|
return index + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected File getColumnRoot(int columnIndex) {
|
||||||
|
// substract checksum column offset
|
||||||
|
return checksumColumns.get(columnIndex - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<File> checksumColumns() {
|
||||||
|
return Collections.unmodifiableList(checksumColumns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getColumnCount() {
|
public int getColumnCount() {
|
||||||
return columns.size() + 2;
|
// add checksum column offset
|
||||||
|
return checksumColumns.size() + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<File> getChecksumColumns() {
|
protected int getRowIndex(ChecksumRow row) {
|
||||||
return Collections.unmodifiableList(columns);
|
return rows.getIndexByKey(row.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected int getRowIndex(ChecksumCell cell) {
|
||||||
|
return rows.getIndexByKey(cell.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<ChecksumRow> rows() {
|
||||||
|
return Collections.unmodifiableList(rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,6 +113,24 @@ class ChecksumTableModel extends AbstractTableModel implements Iterable<Checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setHashType(HashType hashType) {
|
||||||
|
HashType old = this.hashType;
|
||||||
|
|
||||||
|
this.hashType = hashType;
|
||||||
|
|
||||||
|
// update table
|
||||||
|
fireTableDataChanged();
|
||||||
|
|
||||||
|
// notify listeners
|
||||||
|
pcs.firePropertyChange(HASH_TYPE_PROPERTY, old, hashType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public HashType getHashType() {
|
||||||
|
return hashType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
ChecksumRow row = rows.get(rowIndex);
|
ChecksumRow row = rows.get(rowIndex);
|
||||||
|
@ -95,44 +140,81 @@ class ChecksumTableModel extends AbstractTableModel implements Iterable<Checksum
|
||||||
return row.getState();
|
return row.getState();
|
||||||
case 1:
|
case 1:
|
||||||
return row.getName();
|
return row.getName();
|
||||||
default:
|
}
|
||||||
return row.getChecksum(getColumnRoot(columnIndex));
|
|
||||||
|
ChecksumCell cell = row.getChecksum(getColumnRoot(columnIndex));
|
||||||
|
|
||||||
|
// empty cell
|
||||||
|
if (cell == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
switch (cell.getState()) {
|
||||||
|
case READY:
|
||||||
|
return cell.getChecksum(hashType);
|
||||||
|
case ERROR:
|
||||||
|
return cell.getError();
|
||||||
|
default: // PENDING or PROGRESS
|
||||||
|
return cell.getTask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
public void addAll(Collection<ChecksumCell> values) {
|
||||||
public Iterator<ChecksumRow> iterator() {
|
List<ChecksumCell> replacements = new ArrayList<ChecksumCell>();
|
||||||
return rows.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addAll(List<ChecksumCell> list) {
|
|
||||||
int firstRow = getRowCount();
|
|
||||||
|
|
||||||
for (ChecksumCell entry : list) {
|
int rowCount = getRowCount();
|
||||||
ChecksumRow row = rows.getByKey(entry.getName());
|
int columnCount = getColumnCount();
|
||||||
|
|
||||||
|
for (ChecksumCell cell : values) {
|
||||||
|
int rowIndex = getRowIndex(cell);
|
||||||
|
|
||||||
if (row == null) {
|
ChecksumRow row;
|
||||||
row = new ChecksumRow(entry.getName());
|
|
||||||
|
if (rowIndex >= 0) {
|
||||||
|
// get existing row
|
||||||
|
row = rows.get(rowIndex);
|
||||||
|
} else {
|
||||||
|
// add new row
|
||||||
|
row = new ChecksumRow(cell.getName());
|
||||||
|
row.addPropertyChangeListener(stateListener);
|
||||||
rows.add(row);
|
rows.add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
row.put(entry);
|
// add cell to row
|
||||||
|
ChecksumCell old = row.put(cell);
|
||||||
|
|
||||||
|
// dispose of old cell
|
||||||
|
if (old != null) {
|
||||||
|
old.dispose();
|
||||||
|
replacements.add(cell);
|
||||||
|
}
|
||||||
|
|
||||||
// listen to changes (progress, state)
|
// listen to changes (progress, state)
|
||||||
entry.addPropertyChangeListener(progressListener);
|
cell.addPropertyChangeListener(progressListener);
|
||||||
|
|
||||||
if (!columns.contains(entry.getRoot())) {
|
if (!checksumColumns.contains(cell.getRoot())) {
|
||||||
columns.add(entry.getRoot());
|
checksumColumns.add(cell.getRoot());
|
||||||
fireTableStructureChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int lastRow = getRowCount() - 1;
|
// fire table events
|
||||||
|
if (columnCount != getColumnCount()) {
|
||||||
|
// number of columns has changed
|
||||||
|
fireTableStructureChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (lastRow >= firstRow) {
|
for (ChecksumCell replacement : replacements) {
|
||||||
fireTableRowsInserted(firstRow, lastRow);
|
int row = getRowIndex(replacement);
|
||||||
|
|
||||||
|
// update this cell
|
||||||
|
fireTableCellUpdated(row, 0);
|
||||||
|
fireTableCellUpdated(row, getColumnIndex(replacement));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowCount != getRowCount()) {
|
||||||
|
// some rows have been inserted
|
||||||
|
fireTableRowsInserted(rowCount, getRowCount() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,46 +235,40 @@ class ChecksumTableModel extends AbstractTableModel implements Iterable<Checksum
|
||||||
|
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
columns.clear();
|
checksumColumns.clear();
|
||||||
rows.clear();
|
rows.clear();
|
||||||
|
|
||||||
fireTableStructureChanged();
|
fireTableStructureChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final PropertyChangeListener stateListener = new PropertyChangeListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
int row = getRowIndex((ChecksumRow) evt.getSource());
|
||||||
|
|
||||||
|
if (row >= 0) {
|
||||||
|
// update only column 0 (state)
|
||||||
|
fireTableCellUpdated(row, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final PropertyChangeListener progressListener = new PropertyChangeListener() {
|
private final PropertyChangeListener progressListener = new PropertyChangeListener() {
|
||||||
|
|
||||||
private final MutableTableModelEvent mutableUpdateEvent = new MutableTableModelEvent(ChecksumTableModel.this, UPDATE);
|
|
||||||
|
|
||||||
|
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
ChecksumCell entry = (ChecksumCell) evt.getSource();
|
ChecksumCell cell = (ChecksumCell) evt.getSource();
|
||||||
|
|
||||||
int index = rows.getIndexByKey(entry.getName());
|
int row = getRowIndex(cell);
|
||||||
|
int column = getColumnIndex(cell);
|
||||||
|
|
||||||
if (index >= 0) {
|
if (row >= 0 && column >= 0) {
|
||||||
rows.get(index).updateState();
|
fireTableCellUpdated(row, column);
|
||||||
fireTableChanged(mutableUpdateEvent.setRow(index));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
protected static class MutableTableModelEvent extends TableModelEvent {
|
|
||||||
|
|
||||||
public MutableTableModelEvent(TableModel source, int type) {
|
|
||||||
super(source, 0, 0, ALL_COLUMNS, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public MutableTableModelEvent setRow(int row) {
|
|
||||||
this.firstRow = row;
|
|
||||||
this.lastRow = row;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected static abstract class IndexedMap<K, V> extends AbstractList<V> implements Set<V> {
|
protected static abstract class IndexedMap<K, V> extends AbstractList<V> implements Set<V> {
|
||||||
|
|
||||||
private final Map<K, Integer> indexMap = new HashMap<K, Integer>(64);
|
private final Map<K, Integer> indexMap = new HashMap<K, Integer>(64);
|
||||||
|
@ -208,16 +284,6 @@ class ChecksumTableModel extends AbstractTableModel implements Iterable<Checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public V getByKey(K key) {
|
|
||||||
Integer index = indexMap.get(key);
|
|
||||||
|
|
||||||
if (index == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getIndexByKey(K key) {
|
public int getIndexByKey(K key) {
|
||||||
Integer index = indexMap.get(key);
|
Integer index = indexMap.get(key);
|
||||||
|
|
||||||
|
@ -277,4 +343,16 @@ class ChecksumTableModel extends AbstractTableModel implements Iterable<Checksum
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
|
||||||
|
|
||||||
|
|
||||||
|
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
pcs.addPropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
pcs.removePropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.tuned.FileUtilities.containsOnly;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.ui.panel.sfv.VerificationFileScanner.IllegalSyntaxException;
|
||||||
|
import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy;
|
||||||
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
|
||||||
|
|
||||||
|
|
||||||
|
class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumCell> {
|
||||||
|
|
||||||
|
private final ChecksumTableModel model;
|
||||||
|
private final ChecksumComputationService computationService;
|
||||||
|
|
||||||
|
|
||||||
|
public ChecksumTableTransferablePolicy(ChecksumTableModel model, ChecksumComputationService checksumComputationService) {
|
||||||
|
this.model = model;
|
||||||
|
this.computationService = checksumComputationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean accept(List<File> files) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void clear() {
|
||||||
|
super.clear();
|
||||||
|
|
||||||
|
computationService.reset();
|
||||||
|
model.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void prepare(List<File> files) {
|
||||||
|
HashType type = getVerificationType(files);
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
model.setHashType(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void process(List<ChecksumCell> chunks) {
|
||||||
|
model.addAll(chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void process(Exception e) {
|
||||||
|
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected HashType getVerificationType(List<File> files) {
|
||||||
|
for (HashType hash : HashType.values()) {
|
||||||
|
if (containsOnly(files, new ExtensionFileFilter(hash.getExtension()))) {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void loadVerificationFile(File file, HashType type) throws IOException {
|
||||||
|
// don't use new Scanner(File) because of BUG 6368019 (http://bugs.sun.com/view_bug.do?bug_id=6368019)
|
||||||
|
VerificationFileScanner scanner = type.newScanner(new Scanner(new FileInputStream(file), "UTF-8"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// root for relative file paths in verification file
|
||||||
|
File root = file.getParentFile();
|
||||||
|
|
||||||
|
while (scanner.hasNext()) {
|
||||||
|
try {
|
||||||
|
Entry<File, String> entry = scanner.next();
|
||||||
|
|
||||||
|
String name = normalizeRelativePath(entry.getKey());
|
||||||
|
String hash = entry.getValue();
|
||||||
|
|
||||||
|
ChecksumCell correct = new ChecksumCell(name, file, Collections.singletonMap(type, hash));
|
||||||
|
ChecksumCell current = createComputationCell(name, root, type);
|
||||||
|
|
||||||
|
publish(correct, current);
|
||||||
|
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (IllegalSyntaxException e) {
|
||||||
|
// tell user about illegal lines in verification file
|
||||||
|
publish(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
scanner.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ThreadLocal<ExecutorService> executor = new ThreadLocal<ExecutorService>();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void load(List<File> files) throws IOException {
|
||||||
|
// initialize drop parameters
|
||||||
|
executor.set(computationService.newExecutor());
|
||||||
|
|
||||||
|
try {
|
||||||
|
HashType verificationType = getVerificationType(files);
|
||||||
|
|
||||||
|
if (verificationType != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
loadVerificationFile(file, verificationType);
|
||||||
|
}
|
||||||
|
} else if ((files.size() == 1) && files.get(0).isDirectory()) {
|
||||||
|
// one single folder
|
||||||
|
File file = files.get(0);
|
||||||
|
|
||||||
|
for (File f : file.listFiles()) {
|
||||||
|
load(f, null, file);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// bunch of files
|
||||||
|
for (File f : files) {
|
||||||
|
load(f, null, f.getParentFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// supposed to happen if background execution was aborted
|
||||||
|
} finally {
|
||||||
|
// shutdown executor after all tasks have been completed
|
||||||
|
executor.get().shutdown();
|
||||||
|
|
||||||
|
// remove drop parameters
|
||||||
|
executor.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void load(File file, File relativeFile, File root) throws InterruptedException {
|
||||||
|
if (Thread.interrupted())
|
||||||
|
throw new InterruptedException();
|
||||||
|
|
||||||
|
// add next name to relative path
|
||||||
|
relativeFile = new File(relativeFile, file.getName());
|
||||||
|
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
// load all files in the file tree
|
||||||
|
for (File child : file.listFiles()) {
|
||||||
|
load(child, relativeFile, root);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
publish(createComputationCell(normalizeRelativePath(relativeFile), root, model.getHashType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected ChecksumCell createComputationCell(String name, File root, HashType hash) {
|
||||||
|
ChecksumCell cell = new ChecksumCell(name, root, new ChecksumComputationTask(new File(root, name), hash));
|
||||||
|
|
||||||
|
// start computation task
|
||||||
|
executor.get().execute(cell.getTask());
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected String normalizeRelativePath(File file) {
|
||||||
|
if (file.isAbsolute())
|
||||||
|
throw new IllegalArgumentException("Path must be relative");
|
||||||
|
|
||||||
|
return file.getPath().replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFileFilterDescription() {
|
||||||
|
return "files, folders and sfv files";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,33 +2,106 @@
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
|
||||||
enum HashType {
|
enum HashType {
|
||||||
|
|
||||||
CRC32 {
|
SFV {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Hash newInstance() {
|
public Hash newHash() {
|
||||||
return new ChecksumHash(new CRC32());
|
return new ChecksumHash(new CRC32());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VerificationFileScanner newScanner(Scanner scanner) {
|
||||||
|
// adapt default scanner to sfv line syntax
|
||||||
|
return new VerificationFileScanner(scanner) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern used to parse the lines of a sfv file.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Sample:
|
||||||
|
* folder/file.txt 970E4EF1
|
||||||
|
* | Group 1 | | Gr.2 |
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
private final Pattern pattern = Pattern.compile("(.+)\\s+(\\p{XDigit}{8})");
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Entry<File, String> parseLine(String line) {
|
||||||
|
Matcher matcher = pattern.matcher(line);
|
||||||
|
|
||||||
|
if (!matcher.matches())
|
||||||
|
throw new IllegalSyntaxException(getLineNumber(), line);
|
||||||
|
|
||||||
|
return entry(new File(matcher.group(1)), matcher.group(2));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VerificationFilePrinter newPrinter(Formatter out) {
|
||||||
|
return new VerificationFilePrinter(out, "CRC32") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void print(String path, String hash) {
|
||||||
|
// e.g folder/file.txt 970E4EF1
|
||||||
|
out.format(String.format("%s %s", path, hash));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
MD5 {
|
MD5 {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Hash newInstance() {
|
public Hash newHash() {
|
||||||
return new MessageDigestHash("MD5");
|
return new MessageDigestHash("MD5");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
SHA1 {
|
SHA1 {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Hash newInstance() {
|
public Hash newHash() {
|
||||||
return new MessageDigestHash("SHA-1");
|
return new MessageDigestHash("SHA-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SHA-1";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public abstract Hash newInstance();
|
public abstract Hash newHash();
|
||||||
|
|
||||||
|
|
||||||
|
public VerificationFileScanner newScanner(Scanner scanner) {
|
||||||
|
return new VerificationFileScanner(scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public VerificationFilePrinter newPrinter(Formatter out) {
|
||||||
|
return new VerificationFilePrinter(out, this.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getExtension() {
|
||||||
|
return name().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ class HighlightPatternCellRenderer extends DefaultTableCellRenderer {
|
||||||
StringBuffer htmlText = new StringBuffer("<html><nobr>");
|
StringBuffer htmlText = new StringBuffer("<html><nobr>");
|
||||||
|
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
matcher.appendReplacement(htmlText, "<span style='font-size: " + cssFontSize + ";" + (!isSelected ? "color: " + cssColor + ";" : "") + "'>$0</span>");
|
matcher.appendReplacement(htmlText, createReplacement(isSelected));
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher.appendTail(htmlText);
|
matcher.appendTail(htmlText);
|
||||||
|
@ -50,4 +50,21 @@ class HighlightPatternCellRenderer extends DefaultTableCellRenderer {
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected String createReplacement(boolean isSelected) {
|
||||||
|
// build replacement string like
|
||||||
|
// e.g. <span style='font-size: smaller; color: #009900;'>$0</span>
|
||||||
|
StringBuilder replacement = new StringBuilder(60);
|
||||||
|
|
||||||
|
replacement.append("<span style='");
|
||||||
|
replacement.append("font-size:").append(cssFontSize).append(';');
|
||||||
|
|
||||||
|
if (!isSelected) {
|
||||||
|
replacement.append("color:").append(cssColor).append(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
return replacement.append("'>$0</span>").toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
|
||||||
|
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.KeyStroke;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.border.TitledBorder;
|
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
|
||||||
import net.sourceforge.filebot.ui.FileBotPanel;
|
|
||||||
import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
|
|
||||||
import net.sourceforge.filebot.ui.SelectDialog;
|
|
||||||
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
|
||||||
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
|
||||||
import net.sourceforge.filebot.ui.transfer.SaveAction;
|
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
|
||||||
import net.sourceforge.tuned.MessageHandler;
|
|
||||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
|
||||||
|
|
||||||
|
|
||||||
public class SfvPanel extends FileBotPanel {
|
|
||||||
|
|
||||||
private final ChecksumComputationService computationService = new ChecksumComputationService();
|
|
||||||
|
|
||||||
private final SfvTable table = new SfvTable();
|
|
||||||
|
|
||||||
private final SfvTransferablePolicy transferablePolicy = new SfvTransferablePolicy(table.getModel(), computationService);
|
|
||||||
private final ChecksumTableExportHandler exportHandler = new ChecksumTableExportHandler(table.getModel());
|
|
||||||
|
|
||||||
private final MessageHandler messageHandler = new FileTransferableMessageHandler(this, transferablePolicy);
|
|
||||||
|
|
||||||
|
|
||||||
public SfvPanel() {
|
|
||||||
super("SFV", ResourceManager.getIcon("panel.sfv"));
|
|
||||||
|
|
||||||
table.setTransferHandler(new DefaultTransferHandler(transferablePolicy, exportHandler));
|
|
||||||
table.setDragEnabled(true);
|
|
||||||
|
|
||||||
JPanel contentPane = new JPanel(new MigLayout("insets 0, nogrid, fill", null, "align bottom"));
|
|
||||||
contentPane.setBorder(new TitledBorder(getPanelName()));
|
|
||||||
|
|
||||||
setLayout(new MigLayout("insets dialog, fill"));
|
|
||||||
add(contentPane, "grow");
|
|
||||||
|
|
||||||
contentPane.add(new JScrollPane(table), "grow, wrap 10px");
|
|
||||||
|
|
||||||
contentPane.add(new JButton(loadAction), "gap 15px, gap bottom 4px");
|
|
||||||
contentPane.add(new JButton(saveAction), "gap rel, gap bottom 4px");
|
|
||||||
contentPane.add(new JButton(clearAction), "gap rel, gap bottom 4px");
|
|
||||||
|
|
||||||
contentPane.add(new TotalProgressPanel(computationService), "gap left indent:push, gap bottom 2px, gap right 7px, hidemode 3");
|
|
||||||
|
|
||||||
// Shortcut DELETE
|
|
||||||
TunedUtilities.putActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MessageHandler getMessageHandler() {
|
|
||||||
return messageHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final SaveAction saveAction = new ChecksumTableSaveAction();
|
|
||||||
|
|
||||||
private final LoadAction loadAction = new LoadAction(transferablePolicy);
|
|
||||||
|
|
||||||
private final AbstractAction clearAction = new AbstractAction("Clear", ResourceManager.getIcon("action.clear")) {
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
transferablePolicy.reset();
|
|
||||||
computationService.reset();
|
|
||||||
|
|
||||||
table.getModel().clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final AbstractAction removeAction = new AbstractAction("Remove") {
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
if (table.getSelectedRowCount() < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int firstSelectedRow = table.getSelectedRow();
|
|
||||||
|
|
||||||
// remove selected rows
|
|
||||||
table.getModel().remove(table.getSelectedRows());
|
|
||||||
|
|
||||||
// update computation service task count
|
|
||||||
computationService.purge();
|
|
||||||
|
|
||||||
// auto select next row
|
|
||||||
firstSelectedRow = Math.min(firstSelectedRow, table.getRowCount() - 1);
|
|
||||||
|
|
||||||
table.getSelectionModel().setSelectionInterval(firstSelectedRow, firstSelectedRow);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
protected class ChecksumTableSaveAction extends SaveAction {
|
|
||||||
|
|
||||||
private File selectedColumn = null;
|
|
||||||
|
|
||||||
|
|
||||||
public ChecksumTableSaveAction() {
|
|
||||||
super(exportHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChecksumTableExportHandler getExportHandler() {
|
|
||||||
return (ChecksumTableExportHandler) super.getExportHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean canExport() {
|
|
||||||
return selectedColumn != null && super.canExport();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void export(File file) throws IOException {
|
|
||||||
getExportHandler().export(file, selectedColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getDefaultFileName() {
|
|
||||||
return getExportHandler().getDefaultFileName(selectedColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected File getDefaultFolder() {
|
|
||||||
// if the column is a folder use it as default folder in the file dialog
|
|
||||||
return selectedColumn.isDirectory() ? selectedColumn : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
List<File> options = table.getModel().getChecksumColumns();
|
|
||||||
|
|
||||||
this.selectedColumn = null;
|
|
||||||
|
|
||||||
if (options.size() == 1) {
|
|
||||||
// auto-select option if there is only one option
|
|
||||||
this.selectedColumn = options.get(0);
|
|
||||||
} else if (options.size() > 1) {
|
|
||||||
// user must select one option
|
|
||||||
SelectDialog<File> selectDialog = new SelectDialog<File>(SwingUtilities.getWindowAncestor(SfvPanel.this), options) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String convertValueToString(Object value) {
|
|
||||||
return FileUtilities.getFolderName((File) value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
selectDialog.getHeaderLabel().setText("Select checksum column:");
|
|
||||||
selectDialog.setVisible(true);
|
|
||||||
|
|
||||||
this.selectedColumn = selectDialog.getSelectedValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selectedColumn != null) {
|
|
||||||
// continue if a column was selected
|
|
||||||
super.actionPerformed(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
|
||||||
|
|
||||||
|
|
||||||
import static net.sourceforge.filebot.FileBotUtilities.SFV_FILES;
|
|
||||||
import static net.sourceforge.tuned.FileUtilities.containsOnly;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Scanner;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy;
|
|
||||||
import net.sourceforge.tuned.ExceptionUtilities;
|
|
||||||
|
|
||||||
|
|
||||||
class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumCell> {
|
|
||||||
|
|
||||||
private final ChecksumTableModel model;
|
|
||||||
private final ChecksumComputationService checksumComputationService;
|
|
||||||
|
|
||||||
|
|
||||||
public SfvTransferablePolicy(ChecksumTableModel model, ChecksumComputationService checksumComputationService) {
|
|
||||||
this.model = model;
|
|
||||||
this.checksumComputationService = checksumComputationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean accept(List<File> files) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void clear() {
|
|
||||||
checksumComputationService.reset();
|
|
||||||
model.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void process(List<ChecksumCell> chunks) {
|
|
||||||
model.addAll(chunks);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void process(Exception e) {
|
|
||||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void loadSfvFile(File sfvFile, Executor executor) {
|
|
||||||
try {
|
|
||||||
// don't use new Scanner(File) because of BUG 6368019 (http://bugs.sun.com/view_bug.do?bug_id=6368019)
|
|
||||||
Scanner scanner = new Scanner(new FileInputStream(sfvFile), "utf-8");
|
|
||||||
|
|
||||||
try {
|
|
||||||
Pattern pattern = Pattern.compile("(.+)\\s+(\\p{XDigit}{8})");
|
|
||||||
|
|
||||||
// root for relative file paths in sfv file
|
|
||||||
File root = sfvFile.getParentFile();
|
|
||||||
|
|
||||||
while (scanner.hasNextLine()) {
|
|
||||||
String line = scanner.nextLine();
|
|
||||||
|
|
||||||
if (line.startsWith(";"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Matcher matcher = pattern.matcher(line);
|
|
||||||
|
|
||||||
if (!matcher.matches())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
String name = matcher.group(1);
|
|
||||||
String checksum = matcher.group(2);
|
|
||||||
|
|
||||||
ChecksumCell correct = new ChecksumCell(name, sfvFile, Collections.singletonMap(HashType.CRC32, checksum));
|
|
||||||
ChecksumCell current = createChecksumCell(name, root, new File(root, name), executor);
|
|
||||||
|
|
||||||
publish(correct, current);
|
|
||||||
|
|
||||||
if (Thread.interrupted()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
scanner.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// should not happen
|
|
||||||
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFileFilterDescription() {
|
|
||||||
return "files, folders and sfv files";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void load(List<File> files) {
|
|
||||||
ExecutorService executor = checksumComputationService.newExecutor();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (containsOnly(files, SFV_FILES)) {
|
|
||||||
// one or more sfv files
|
|
||||||
for (File file : files) {
|
|
||||||
loadSfvFile(file, executor);
|
|
||||||
}
|
|
||||||
} else if ((files.size() == 1) && files.get(0).isDirectory()) {
|
|
||||||
// one single folder
|
|
||||||
File file = files.get(0);
|
|
||||||
|
|
||||||
for (File f : file.listFiles()) {
|
|
||||||
load(f, file, "", executor);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// bunch of files
|
|
||||||
for (File f : files) {
|
|
||||||
load(f, f.getParentFile(), "", executor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// supposed to happen if background execution was aborted
|
|
||||||
} finally {
|
|
||||||
executor.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void load(File file, File root, String prefix, Executor executor) throws InterruptedException {
|
|
||||||
if (Thread.interrupted())
|
|
||||||
throw new InterruptedException();
|
|
||||||
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
// load all files in the file tree
|
|
||||||
String newPrefix = prefix + file.getName() + "/";
|
|
||||||
|
|
||||||
for (File f : file.listFiles()) {
|
|
||||||
load(f, root, newPrefix, executor);
|
|
||||||
}
|
|
||||||
} else if (file.isFile()) {
|
|
||||||
publish(createChecksumCell(prefix + file.getName(), root, file, executor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected ChecksumCell createChecksumCell(String name, File root, File file, Executor executor) {
|
|
||||||
ChecksumCell cell = new ChecksumCell(name, root, new ChecksumComputationTask(new File(root, name), HashType.CRC32));
|
|
||||||
|
|
||||||
// start computation task
|
|
||||||
executor.execute(cell.getTask());
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,12 +15,12 @@ import net.sourceforge.filebot.ResourceManager;
|
||||||
import net.sourceforge.filebot.ui.panel.sfv.ChecksumRow.State;
|
import net.sourceforge.filebot.ui.panel.sfv.ChecksumRow.State;
|
||||||
|
|
||||||
|
|
||||||
class StateIconTableCellRenderer extends DefaultTableCellRenderer {
|
class StateIconCellRenderer extends DefaultTableCellRenderer {
|
||||||
|
|
||||||
private final Map<State, Icon> icons = new EnumMap<State, Icon>(State.class);
|
private final Map<State, Icon> icons = new EnumMap<State, Icon>(State.class);
|
||||||
|
|
||||||
|
|
||||||
public StateIconTableCellRenderer() {
|
public StateIconCellRenderer() {
|
||||||
icons.put(State.UNKNOWN, ResourceManager.getIcon("status.unknown"));
|
icons.put(State.UNKNOWN, ResourceManager.getIcon("status.unknown"));
|
||||||
icons.put(State.OK, ResourceManager.getIcon("status.ok"));
|
icons.put(State.OK, ResourceManager.getIcon("status.ok"));
|
||||||
icons.put(State.WARNING, ResourceManager.getIcon("status.warning"));
|
icons.put(State.WARNING, ResourceManager.getIcon("status.warning"));
|
|
@ -0,0 +1,74 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JProgressBar;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.table.TableCellRenderer;
|
||||||
|
|
||||||
|
|
||||||
|
class SwingWorkerCellRenderer extends JPanel implements TableCellRenderer {
|
||||||
|
|
||||||
|
private final JProgressBar progressBar = new JProgressBar(0, 100);
|
||||||
|
|
||||||
|
|
||||||
|
public SwingWorkerCellRenderer() {
|
||||||
|
super(new BorderLayout());
|
||||||
|
|
||||||
|
// create margin for progress bar,
|
||||||
|
// because setting margin for progress bar directly does not work (border size is not respected in the paint method)
|
||||||
|
setBorder(new EmptyBorder(2, 2, 2, 2));
|
||||||
|
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
|
||||||
|
add(progressBar, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
|
||||||
|
|
||||||
|
progressBar.setValue(((SwingWorker<?, ?>) value).getProgress());
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden for performance reasons.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void repaint(long tm, int x, int y, int width, int height) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden for performance reasons.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void repaint(Rectangle r) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden for performance reasons.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void repaint() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden for performance reasons.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,41 +4,36 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
import static net.sourceforge.filebot.ui.panel.sfv.ChecksumComputationService.TASK_COUNT_PROPERTY;
|
import static net.sourceforge.filebot.ui.panel.sfv.ChecksumComputationService.TASK_COUNT_PROPERTY;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JProgressBar;
|
import javax.swing.JProgressBar;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.Timer;
|
import javax.swing.Timer;
|
||||||
|
import javax.swing.border.TitledBorder;
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
|
||||||
|
|
||||||
|
|
||||||
class TotalProgressPanel extends JComponent {
|
class TotalProgressPanel extends JComponent {
|
||||||
|
|
||||||
private int millisToSetVisible = 200;
|
|
||||||
|
|
||||||
private final JProgressBar progressBar = new JProgressBar(0, 0);
|
private final JProgressBar progressBar = new JProgressBar(0, 0);
|
||||||
|
|
||||||
private final ChecksumComputationService computationService;
|
private final int millisToSetVisible = 200;
|
||||||
|
|
||||||
|
|
||||||
public TotalProgressPanel(ChecksumComputationService computationService) {
|
public TotalProgressPanel(ChecksumComputationService computationService) {
|
||||||
this.computationService = computationService;
|
setLayout(new MigLayout("insets 1px"));
|
||||||
|
|
||||||
setLayout(new MigLayout());
|
setBorder(new TitledBorder("Total Progress"));
|
||||||
|
|
||||||
// invisible by default
|
// invisible by default
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
|
||||||
progressBar.setStringPainted(true);
|
progressBar.setStringPainted(true);
|
||||||
progressBar.setBorderPainted(false);
|
|
||||||
progressBar.setString("");
|
|
||||||
|
|
||||||
setBorder(BorderFactory.createTitledBorder("Total Progress"));
|
|
||||||
|
|
||||||
add(progressBar, "growx");
|
add(progressBar, "growx");
|
||||||
|
|
||||||
|
@ -47,45 +42,82 @@ class TotalProgressPanel extends JComponent {
|
||||||
|
|
||||||
private final PropertyChangeListener progressListener = new PropertyChangeListener() {
|
private final PropertyChangeListener progressListener = new PropertyChangeListener() {
|
||||||
|
|
||||||
private Timer setVisibleTimer;
|
private static final String SHOW = "show";
|
||||||
|
private static final String HIDE = "hide";
|
||||||
|
|
||||||
|
private final DelayedToggle delayed = new DelayedToggle();
|
||||||
|
|
||||||
|
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
final int completedTaskCount = computationService.getCompletedTaskCount();
|
final int completedTaskCount = getComputationService(evt).getCompletedTaskCount();
|
||||||
final int totalTaskCount = computationService.getTotalTaskCount();
|
final int totalTaskCount = getComputationService(evt).getTotalTaskCount();
|
||||||
|
|
||||||
// invoke on EDT
|
// invoke on EDT
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (completedTaskCount < totalTaskCount) {
|
|
||||||
if (setVisibleTimer == null) {
|
if (completedTaskCount == totalTaskCount) {
|
||||||
setVisibleTimer = TunedUtilities.invokeLater(millisToSetVisible, new Runnable() {
|
// delayed hide on reset, immediate hide on finish
|
||||||
|
delayed.toggle(HIDE, totalTaskCount == 0 ? millisToSetVisible : 0, visibilityActionHandler);
|
||||||
@Override
|
} else if (totalTaskCount != 0) {
|
||||||
public void run() {
|
delayed.toggle(SHOW, millisToSetVisible, visibilityActionHandler);
|
||||||
setVisible(computationService.getTaskCount() > computationService.getCompletedTaskCount());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (setVisibleTimer != null) {
|
|
||||||
setVisibleTimer.stop();
|
|
||||||
setVisibleTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide when not active
|
|
||||||
setVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
progressBar.setValue(completedTaskCount);
|
if (totalTaskCount != 0) {
|
||||||
progressBar.setMaximum(totalTaskCount);
|
progressBar.setValue(completedTaskCount);
|
||||||
|
progressBar.setMaximum(totalTaskCount);
|
||||||
progressBar.setString(String.format("%d / %d", completedTaskCount, totalTaskCount));
|
|
||||||
|
progressBar.setString(String.format("%d / %d", completedTaskCount, totalTaskCount));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ChecksumComputationService getComputationService(PropertyChangeEvent evt) {
|
||||||
|
return ((ChecksumComputationService) evt.getSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ActionListener visibilityActionHandler = new ActionListener() {
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
setVisible(e.getActionCommand() == SHOW);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
protected static class DelayedToggle {
|
||||||
|
|
||||||
|
private Timer timer = null;
|
||||||
|
|
||||||
|
|
||||||
|
public void toggle(String action, int delay, final ActionListener actionHandler) {
|
||||||
|
if (timer != null) {
|
||||||
|
if (action.equals(timer.getActionCommand())) {
|
||||||
|
// action has not changed, don't stop existing timer
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
timer = new Timer(delay, new ActionListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
actionHandler.actionPerformed(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
timer.setActionCommand(action);
|
||||||
|
timer.setRepeats(false);
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Formatter;
|
||||||
|
|
||||||
|
|
||||||
|
class VerificationFilePrinter implements Closeable {
|
||||||
|
|
||||||
|
protected final Formatter out;
|
||||||
|
protected final String algorithm;
|
||||||
|
|
||||||
|
|
||||||
|
public VerificationFilePrinter(Formatter out, String algorithm) {
|
||||||
|
this.out = out;
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void println(String path, String hash) {
|
||||||
|
// print entry
|
||||||
|
print(path, hash);
|
||||||
|
|
||||||
|
// print line separator
|
||||||
|
out.format("%n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void print(String path, String hash) {
|
||||||
|
// e.g. 1a02a7c1e9ac91346d08829d5037b240f42ded07 ?SHA1*folder/file.txt
|
||||||
|
out.format("%s ?%s*%s", hash, algorithm.toUpperCase(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.AbstractMap.SimpleEntry;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
|
class VerificationFileScanner implements Iterator<Entry<File, String>>, Closeable {
|
||||||
|
|
||||||
|
private final Scanner scanner;
|
||||||
|
|
||||||
|
private String cache;
|
||||||
|
|
||||||
|
private int lineNumber = 0;
|
||||||
|
|
||||||
|
|
||||||
|
public VerificationFileScanner(File file) throws FileNotFoundException {
|
||||||
|
// don't use new Scanner(File) because of BUG 6368019 (http://bugs.sun.com/view_bug.do?bug_id=6368019)
|
||||||
|
this(new Scanner(new FileInputStream(file), "UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public VerificationFileScanner(Scanner scanner) {
|
||||||
|
this.scanner = scanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (cache == null) {
|
||||||
|
// cache next line
|
||||||
|
cache = nextLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entry<File, String> next() {
|
||||||
|
// cache next line
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return parseLine(cache);
|
||||||
|
} finally {
|
||||||
|
// invalidate cache
|
||||||
|
cache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected String nextLine() {
|
||||||
|
String line = null;
|
||||||
|
|
||||||
|
// get next non-comment line
|
||||||
|
while (scanner.hasNext() && (line == null || isComment(line))) {
|
||||||
|
line = scanner.nextLine().trim();
|
||||||
|
lineNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern used to parse the lines of a md5 or sha1 file.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Sample MD5:
|
||||||
|
* 50e85fe18e17e3616774637a82968f4c *folder/file.txt
|
||||||
|
* | Group 1 | Group 2 |
|
||||||
|
*
|
||||||
|
* Sample SHA-1:
|
||||||
|
* 1a02a7c1e9ac91346d08829d5037b240f42ded07 ?SHA1*folder/file.txt
|
||||||
|
* | Group 1 | | Group 2 |
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
private final Pattern pattern = Pattern.compile("(\\p{XDigit}{8,})\\s+(?:\\?\\w+)?\\*(.+)");
|
||||||
|
|
||||||
|
|
||||||
|
protected Entry<File, String> parseLine(String line) {
|
||||||
|
Matcher matcher = pattern.matcher(line);
|
||||||
|
|
||||||
|
if (!matcher.matches())
|
||||||
|
throw new IllegalSyntaxException(getLineNumber(), line);
|
||||||
|
|
||||||
|
return entry(new File(matcher.group(2)), matcher.group(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getLineNumber() {
|
||||||
|
return lineNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected boolean isComment(String line) {
|
||||||
|
return line.startsWith(";") || line.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected Entry<File, String> entry(File file, String hash) {
|
||||||
|
return new SimpleEntry<File, String>(file, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
scanner.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class IllegalSyntaxException extends RuntimeException {
|
||||||
|
|
||||||
|
public IllegalSyntaxException(int lineNumber, String line) {
|
||||||
|
this(String.format("Illegal syntax in line %d: %s", lineNumber, line));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public IllegalSyntaxException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,44 +3,46 @@ package net.sourceforge.filebot.ui.transfer;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.datatransfer.Transferable;
|
import java.awt.datatransfer.Transferable;
|
||||||
import java.beans.PropertyChangeEvent;
|
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.beans.PropertyChangeSupport;
|
import java.beans.PropertyChangeSupport;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
|
import javax.swing.event.SwingPropertyChangeSupport;
|
||||||
|
|
||||||
|
|
||||||
public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferablePolicy {
|
public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferablePolicy {
|
||||||
|
|
||||||
public static final String LOADING_PROPERTY = "loading";
|
public static final String LOADING_PROPERTY = "loading";
|
||||||
|
|
||||||
private final ThreadLocal<BackgroundWorker> threadLocalWorker = new ThreadLocal<BackgroundWorker>() {
|
private final ThreadLocal<BackgroundWorker> threadLocalWorker = new ThreadLocal<BackgroundWorker>();
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BackgroundWorker initialValue() {
|
|
||||||
// fail if a non-background-worker thread is trying to access the thread-local worker object
|
|
||||||
throw new IllegalThreadStateException("Illegal access thread");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final List<BackgroundWorker> workers = new ArrayList<BackgroundWorker>(2);
|
private final List<BackgroundWorker> workers = new ArrayList<BackgroundWorker>(2);
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleTransferable(Transferable tr, TransferAction action) {
|
public void handleTransferable(Transferable tr, TransferAction action) throws Exception {
|
||||||
List<File> files = getFilesFromTransferable(tr);
|
List<File> files = getFilesFromTransferable(tr);
|
||||||
|
|
||||||
if (action != TransferAction.ADD)
|
if (action != TransferAction.ADD) {
|
||||||
clear();
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare(files);
|
||||||
|
|
||||||
// create and start worker
|
// create and start worker
|
||||||
new BackgroundWorker(files).execute();
|
new BackgroundWorker(files).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void prepare(List<File> files) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void clear() {
|
protected void clear() {
|
||||||
// stop other workers on clear (before starting new worker)
|
// stop other workers on clear (before starting new worker)
|
||||||
|
@ -61,14 +63,39 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isLoading() {
|
||||||
|
synchronized (workers) {
|
||||||
|
return !workers.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected abstract void process(List<V> chunks);
|
protected abstract void process(List<V> chunks);
|
||||||
|
|
||||||
|
|
||||||
protected abstract void process(Exception e);
|
protected abstract void process(Exception exception);
|
||||||
|
|
||||||
|
|
||||||
protected final void publish(V... chunks) {
|
protected final void publish(V... chunks) {
|
||||||
threadLocalWorker.get().offer(chunks);
|
BackgroundWorker worker = threadLocalWorker.get();
|
||||||
|
|
||||||
|
if (worker == null) {
|
||||||
|
// fail if a non-background-worker thread is trying to access the thread-local worker object
|
||||||
|
throw new IllegalThreadStateException("Illegal access thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.offer(chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected final void publish(final Exception exception) {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
process(exception);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,7 +117,7 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object doInBackground() {
|
protected Object doInBackground() throws Exception {
|
||||||
// associate this worker with the current (background) thread
|
// associate this worker with the current (background) thread
|
||||||
threadLocalWorker.set(this);
|
threadLocalWorker.set(this);
|
||||||
|
|
||||||
|
@ -121,13 +148,6 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void done() {
|
protected void done() {
|
||||||
// unregister worker
|
|
||||||
synchronized (workers) {
|
|
||||||
if (workers.remove(this) && workers.isEmpty()) {
|
|
||||||
swingPropertyChangeSupport.firePropertyChange(LOADING_PROPERTY, true, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCancelled()) {
|
if (!isCancelled()) {
|
||||||
try {
|
try {
|
||||||
// check for exception
|
// check for exception
|
||||||
|
@ -136,27 +156,17 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
|
||||||
BackgroundFileTransferablePolicy.this.process(e);
|
BackgroundFileTransferablePolicy.this.process(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unregister worker
|
||||||
|
synchronized (workers) {
|
||||||
|
if (workers.remove(this) && workers.isEmpty()) {
|
||||||
|
swingPropertyChangeSupport.firePropertyChange(LOADING_PROPERTY, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final PropertyChangeSupport swingPropertyChangeSupport = new PropertyChangeSupport(this) {
|
protected final PropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport(this, true);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void firePropertyChange(final PropertyChangeEvent evt) {
|
|
||||||
if (SwingUtilities.isEventDispatchThread()) {
|
|
||||||
super.firePropertyChange(evt);
|
|
||||||
} else {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
swingPropertyChangeSupport.firePropertyChange(evt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
|
|
@ -4,9 +4,6 @@ package net.sourceforge.filebot.ui.transfer;
|
||||||
|
|
||||||
import java.awt.datatransfer.Clipboard;
|
import java.awt.datatransfer.Clipboard;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
import javax.swing.JTable;
|
import javax.swing.JTable;
|
||||||
|
@ -16,56 +13,58 @@ import javax.swing.tree.TreePath;
|
||||||
|
|
||||||
public class DefaultClipboardHandler implements ClipboardHandler {
|
public class DefaultClipboardHandler implements ClipboardHandler {
|
||||||
|
|
||||||
|
protected final String newLine = System.getProperty("line.separator");
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exportToClipboard(JComponent component, Clipboard clip, int action) throws IllegalStateException {
|
public void exportToClipboard(JComponent component, Clipboard clip, int action) throws IllegalStateException {
|
||||||
StringWriter buffer = new StringWriter();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
if (component instanceof JList) {
|
if (component instanceof JList) {
|
||||||
export(new PrintWriter(buffer), (JList) component);
|
export(sb, (JList) component);
|
||||||
} else if (component instanceof JTree) {
|
} else if (component instanceof JTree) {
|
||||||
export(new PrintWriter(buffer), (JTree) component);
|
export(sb, (JTree) component);
|
||||||
} else if (component instanceof JTable) {
|
} else if (component instanceof JTable) {
|
||||||
export(new PrintWriter(buffer), (JTable) component);
|
export(sb, (JTable) component);
|
||||||
}
|
}
|
||||||
|
|
||||||
clip.setContents(new StringSelection(buffer.toString()), null);
|
clip.setContents(new StringSelection(sb.toString()), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void export(PrintWriter out, JList list) {
|
protected void export(StringBuilder sb, JList list) {
|
||||||
for (Object value : list.getSelectedValues()) {
|
for (Object value : list.getSelectedValues()) {
|
||||||
out.println(valueToString(value));
|
sb.append(value == null ? "" : value).append(newLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void export(PrintWriter out, JTree tree) {
|
protected void export(StringBuilder sb, JTree tree) {
|
||||||
for (TreePath path : tree.getSelectionPaths()) {
|
for (TreePath path : tree.getSelectionPaths()) {
|
||||||
out.println(valueToString(path.getLastPathComponent()));
|
Object value = path.getLastPathComponent();
|
||||||
|
|
||||||
|
sb.append(value == null ? "" : value).append(newLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void export(PrintWriter out, JTable table) {
|
protected void export(StringBuilder sb, JTable table) {
|
||||||
for (int row : table.getSelectedRows()) {
|
for (int row : table.getSelectedRows()) {
|
||||||
for (int columnIndex = 0; columnIndex < table.getColumnCount(); columnIndex++) {
|
int modelRow = table.getRowSorter().convertRowIndexToModel(row);
|
||||||
out.print(valueToString(table.getModel().getValueAt(row, columnIndex)));
|
|
||||||
|
for (int column = 0; column < table.getColumnCount(); column++) {
|
||||||
|
Object value = table.getModel().getValueAt(modelRow, column);
|
||||||
|
|
||||||
if (columnIndex < table.getColumnCount() - 1)
|
if (value != null) {
|
||||||
out.print("\t");
|
sb.append(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column < table.getColumnCount() - 1) {
|
||||||
|
sb.append("\t");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out.println();
|
sb.append(newLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String valueToString(Object value) {
|
|
||||||
// return empty string for null values
|
|
||||||
if (value == null)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ package net.sourceforge.filebot.ui.transfer;
|
||||||
|
|
||||||
import java.awt.datatransfer.DataFlavor;
|
import java.awt.datatransfer.DataFlavor;
|
||||||
import java.awt.datatransfer.Transferable;
|
import java.awt.datatransfer.Transferable;
|
||||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -27,7 +26,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(Transferable tr) {
|
public boolean accept(Transferable tr) throws Exception {
|
||||||
List<File> files = getFilesFromTransferable(tr);
|
List<File> files = getFilesFromTransferable(tr);
|
||||||
|
|
||||||
if (files.isEmpty())
|
if (files.isEmpty())
|
||||||
|
@ -38,48 +37,40 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected List<File> getFilesFromTransferable(Transferable tr) {
|
protected List<File> getFilesFromTransferable(Transferable tr) throws Exception {
|
||||||
try {
|
if (tr.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
||||||
if (tr.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
// file list flavor
|
||||||
// file list flavor
|
return (List<File>) tr.getTransferData(DataFlavor.javaFileListFlavor);
|
||||||
return (List<File>) tr.getTransferData(DataFlavor.javaFileListFlavor);
|
} else if (tr.isDataFlavorSupported(FileTransferable.uriListFlavor)) {
|
||||||
} else if (tr.isDataFlavorSupported(FileTransferable.uriListFlavor)) {
|
// file URI list flavor
|
||||||
// file URI list flavor
|
String transferData = (String) tr.getTransferData(FileTransferable.uriListFlavor);
|
||||||
String transferData = (String) tr.getTransferData(FileTransferable.uriListFlavor);
|
|
||||||
|
Scanner scanner = new Scanner(transferData).useDelimiter(LINE_SEPARATOR);
|
||||||
|
|
||||||
|
List<File> files = new ArrayList<File>();
|
||||||
|
|
||||||
|
while (scanner.hasNext()) {
|
||||||
|
String uri = scanner.next();
|
||||||
|
|
||||||
Scanner scanner = new Scanner(transferData).useDelimiter(LINE_SEPARATOR);
|
if (uri.startsWith("#")) {
|
||||||
|
// the line is a comment (as per RFC 2483)
|
||||||
ArrayList<File> files = new ArrayList<File>();
|
continue;
|
||||||
|
|
||||||
while (scanner.hasNext()) {
|
|
||||||
String uri = scanner.next();
|
|
||||||
|
|
||||||
if (uri.startsWith("#")) {
|
|
||||||
// the line is a comment (as per RFC 2483)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
File file = new File(new URI(uri));
|
|
||||||
|
|
||||||
if (!file.exists())
|
|
||||||
throw new FileNotFoundException(file.toString());
|
|
||||||
|
|
||||||
files.add(file);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// URISyntaxException, IllegalArgumentException, FileNotFoundException
|
|
||||||
Logger.getLogger("global").log(Level.WARNING, "Invalid file uri: " + uri);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return files;
|
try {
|
||||||
|
File file = new File(new URI(uri));
|
||||||
|
|
||||||
|
if (!file.exists())
|
||||||
|
throw new FileNotFoundException(file.toString());
|
||||||
|
|
||||||
|
files.add(file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// URISyntaxException, IllegalArgumentException, FileNotFoundException
|
||||||
|
Logger.getLogger("global").log(Level.WARNING, "Invalid file uri: " + uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (UnsupportedFlavorException e) {
|
|
||||||
// should not happen
|
return files;
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// should not happen
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
@ -87,7 +78,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleTransferable(Transferable tr, TransferAction action) {
|
public void handleTransferable(Transferable tr, TransferAction action) throws Exception {
|
||||||
List<File> files = getFilesFromTransferable(tr);
|
List<File> files = getFilesFromTransferable(tr);
|
||||||
|
|
||||||
if (action == TransferAction.PUT) {
|
if (action == TransferAction.PUT) {
|
||||||
|
@ -101,7 +92,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
|
||||||
protected abstract boolean accept(List<File> files);
|
protected abstract boolean accept(List<File> files);
|
||||||
|
|
||||||
|
|
||||||
protected abstract void load(List<File> files);
|
protected abstract void load(List<File> files) throws IOException;
|
||||||
|
|
||||||
|
|
||||||
protected abstract void clear();
|
protected abstract void clear();
|
||||||
|
|
|
@ -3,6 +3,8 @@ package net.sourceforge.filebot.ui.transfer;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.JFileChooser;
|
import javax.swing.JFileChooser;
|
||||||
|
@ -23,7 +25,7 @@ public class LoadAction extends AbstractAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
// get transferable policy from action properties
|
// get transferable policy from action properties
|
||||||
TransferablePolicy transferablePolicy = (TransferablePolicy) getValue(TRANSFERABLE_POLICY);
|
TransferablePolicy transferablePolicy = (TransferablePolicy) getValue(TRANSFERABLE_POLICY);
|
||||||
|
|
||||||
|
@ -45,10 +47,17 @@ public class LoadAction extends AbstractAction {
|
||||||
TransferAction action = TransferAction.PUT;
|
TransferAction action = TransferAction.PUT;
|
||||||
|
|
||||||
// if CTRL was pressed when the button was clicked, assume ADD action (same as with dnd)
|
// if CTRL was pressed when the button was clicked, assume ADD action (same as with dnd)
|
||||||
if ((e.getModifiers() & ActionEvent.CTRL_MASK) != 0)
|
if ((evt.getModifiers() & ActionEvent.CTRL_MASK) != 0) {
|
||||||
action = TransferAction.ADD;
|
action = TransferAction.ADD;
|
||||||
|
}
|
||||||
|
|
||||||
if (transferablePolicy.accept(transferable))
|
try {
|
||||||
transferablePolicy.handleTransferable(transferable, action);
|
if (transferablePolicy.accept(transferable)) {
|
||||||
|
transferablePolicy.handleTransferable(transferable, action);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,13 @@ import javax.swing.TransferHandler.TransferSupport;
|
||||||
|
|
||||||
public abstract class TransferablePolicy {
|
public abstract class TransferablePolicy {
|
||||||
|
|
||||||
public abstract boolean accept(Transferable tr);
|
public abstract boolean accept(Transferable tr) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public abstract void handleTransferable(Transferable tr, TransferAction action);
|
public abstract void handleTransferable(Transferable tr, TransferAction action) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public boolean canImport(TransferSupport support) {
|
public boolean canImport(TransferSupport support) {
|
||||||
|
|
||||||
if (support.isDrop())
|
if (support.isDrop())
|
||||||
support.setShowDropLocation(false);
|
support.setShowDropLocation(false);
|
||||||
|
|
||||||
|
@ -32,6 +31,8 @@ public abstract class TransferablePolicy {
|
||||||
|
|
||||||
// just assume that the transferable will be accepted, accept will be called in importData again anyway
|
// just assume that the transferable will be accepted, accept will be called in importData again anyway
|
||||||
return true;
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import java.io.File;
|
||||||
import javax.swing.filechooser.FileFilter;
|
import javax.swing.filechooser.FileFilter;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class TransferablePolicyFileFilter extends FileFilter {
|
public class TransferablePolicyFileFilter extends FileFilter {
|
||||||
|
|
||||||
private final TransferablePolicy transferablePolicy;
|
private final TransferablePolicy transferablePolicy;
|
||||||
|
@ -23,7 +22,11 @@ public class TransferablePolicyFileFilter extends FileFilter {
|
||||||
if (f.isDirectory())
|
if (f.isDirectory())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return transferablePolicy.accept(new FileTransferable(f));
|
try {
|
||||||
|
return transferablePolicy.accept(new FileTransferable(f));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,11 @@ public class DefaultThreadFactory implements ThreadFactory {
|
||||||
public Thread newThread(Runnable r) {
|
public Thread newThread(Runnable r) {
|
||||||
Thread thread = new Thread(group, r, String.format("%s-thread-%d", group.getName(), threadNumber.incrementAndGet()));
|
Thread thread = new Thread(group, r, String.format("%s-thread-%d", group.getName(), threadNumber.incrementAndGet()));
|
||||||
|
|
||||||
thread.setDaemon(daemon);
|
if (daemon != thread.isDaemon())
|
||||||
thread.setPriority(priority);
|
thread.setDaemon(daemon);
|
||||||
|
|
||||||
|
if (priority != thread.getPriority())
|
||||||
|
thread.setPriority(priority);
|
||||||
|
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ public final class ExceptionUtilities {
|
||||||
String message = t.getMessage();
|
String message = t.getMessage();
|
||||||
|
|
||||||
if (message == null || message.isEmpty()) {
|
if (message == null || message.isEmpty()) {
|
||||||
return t.toString();
|
message = t.toString().replaceAll(t.getClass().getName(), t.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
|
|
@ -6,6 +6,8 @@ import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
@ -73,7 +75,11 @@ public class MessageBus {
|
||||||
|
|
||||||
private void publishDirect(String topic, Object... messages) {
|
private void publishDirect(String topic, Object... messages) {
|
||||||
for (MessageHandler handler : getHandlers(topic.toLowerCase())) {
|
for (MessageHandler handler : getHandlers(topic.toLowerCase())) {
|
||||||
handler.handle(topic.toLowerCase(), messages);
|
try {
|
||||||
|
handler.handle(topic.toLowerCase(), messages);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger("global").log(Level.SEVERE, e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,6 @@ package net.sourceforge.tuned;
|
||||||
|
|
||||||
public interface MessageHandler {
|
public interface MessageHandler {
|
||||||
|
|
||||||
public void handle(String topic, Object... messages);
|
public void handle(String topic, Object... messages) throws Exception;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,11 @@ public class ActionPopup extends JPopupMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
actionPanel.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLabel(String label) {
|
public void setLabel(String label) {
|
||||||
headerLabel.setText(label);
|
headerLabel.setText(label);
|
||||||
|
|
|
@ -93,7 +93,6 @@ public final class TunedUtilities {
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
timer.setRepeats(false);
|
timer.setRepeats(false);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||||
|
|
||||||
|
|
||||||
@RunWith(Suite.class)
|
@RunWith(Suite.class)
|
||||||
@SuiteClasses( { SimilarityTestSuite.class, WebTestSuite.class, ArgumentBeanTest.class })
|
@SuiteClasses( { SimilarityTestSuite.class, WebTestSuite.class, MiscSuite.class })
|
||||||
public class FileBotTestSuite {
|
public class FileBotTestSuite {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot;
|
||||||
|
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.ui.panel.sfv.VerificationFileScannerTest;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Suite;
|
||||||
|
import org.junit.runners.Suite.SuiteClasses;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(Suite.class)
|
||||||
|
@SuiteClasses( { ArgumentBeanTest.class, VerificationFileScannerTest.class })
|
||||||
|
public class MiscSuite {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
|
public class VerificationFileScannerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nextLine() {
|
||||||
|
// trim lines, skip empty lines
|
||||||
|
String text = String.format("%s %n %n%n%n %s%n%s", "line 1", "line 2", "line 3");
|
||||||
|
|
||||||
|
VerificationFileScanner lines = new VerificationFileScanner(new Scanner(text));
|
||||||
|
|
||||||
|
assertEquals("line 1", lines.nextLine());
|
||||||
|
assertEquals("line 2", lines.nextLine());
|
||||||
|
assertEquals("line 3", lines.nextLine());
|
||||||
|
|
||||||
|
assertFalse(lines.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseLine() {
|
||||||
|
VerificationFileScanner reader = new VerificationFileScanner(new Scanner("null"));
|
||||||
|
|
||||||
|
// md5
|
||||||
|
Entry<File, String> md5 = reader.parseLine("50e85fe18e17e3616774637a82968f4c *folder/file.txt");
|
||||||
|
|
||||||
|
assertEquals("file.txt", md5.getKey().getName());
|
||||||
|
assertEquals("folder", md5.getKey().getParent());
|
||||||
|
assertEquals("50e85fe18e17e3616774637a82968f4c", md5.getValue());
|
||||||
|
|
||||||
|
// sha1
|
||||||
|
Entry<File, String> sha1 = reader.parseLine("1a02a7c1e9ac91346d08829d5037b240f42ded07 ?SHA1*folder/file.txt");
|
||||||
|
|
||||||
|
assertEquals("file.txt", sha1.getKey().getName());
|
||||||
|
assertEquals("folder", sha1.getKey().getParent());
|
||||||
|
assertEquals("1a02a7c1e9ac91346d08829d5037b240f42ded07", sha1.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue