* 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.list.ListPanel;
|
||||
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.tuned.MessageBus;
|
||||
import net.sourceforge.tuned.MessageHandler;
|
||||
|
@ -85,7 +85,7 @@ public class FileBotWindow extends JFrame implements ListSelectionListener {
|
|||
panels.add(new AnalyzePanel());
|
||||
panels.add(new EpisodeListPanel());
|
||||
panels.add(new SubtitlePanel());
|
||||
panels.add(new SfvPanel());
|
||||
panels.add(new ChecksumPanel());
|
||||
|
||||
return panels;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public class FileTransferableMessageHandler implements MessageHandler {
|
|||
|
||||
|
||||
@Override
|
||||
public void handle(String topic, Object... messages) {
|
||||
public void handle(String topic, Object... messages) throws Exception {
|
||||
// switch to panel
|
||||
MessageBus.getDefault().publish("panel", panel);
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<Abstra
|
|||
protected void load(List<File> files) {
|
||||
try {
|
||||
for (File file : files) {
|
||||
// use fast file to minimize system calls like length(), isDirectory(), isFile(), ...
|
||||
AbstractTreeNode node = getTreeNode(new FastFile(file.getPath()));
|
||||
|
||||
// publish on EDT
|
||||
|
|
|
@ -11,9 +11,6 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtilities;
|
||||
import net.sourceforge.filebot.torrent.Torrent;
|
||||
import net.sourceforge.filebot.ui.FileBotList;
|
||||
|
@ -44,7 +41,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
|
|||
|
||||
|
||||
@Override
|
||||
protected void load(List<File> files) {
|
||||
protected void load(List<File> files) throws IOException {
|
||||
// set title based on parent folder of first file
|
||||
list.setTitle(FileUtilities.getFolderName(files.get(0).getParentFile()));
|
||||
|
||||
|
@ -70,26 +67,21 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
|
|||
}
|
||||
|
||||
|
||||
private void loadTorrents(List<File> torrentFiles) {
|
||||
try {
|
||||
List<Torrent> torrents = new ArrayList<Torrent>(torrentFiles.size());
|
||||
|
||||
for (File file : torrentFiles) {
|
||||
torrents.add(new Torrent(file));
|
||||
private void loadTorrents(List<File> torrentFiles) throws IOException {
|
||||
List<Torrent> torrents = new ArrayList<Torrent>(torrentFiles.size());
|
||||
|
||||
for (File file : torrentFiles) {
|
||||
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.UnsupportedFlavorException;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -45,7 +46,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
|
|||
|
||||
|
||||
@Override
|
||||
public boolean accept(Transferable tr) {
|
||||
public boolean accept(Transferable tr) throws Exception {
|
||||
return tr.isDataFlavorSupported(stringFlavor) || super.accept(tr);
|
||||
}
|
||||
|
||||
|
@ -57,7 +58,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
|
|||
|
||||
|
||||
@Override
|
||||
public void handleTransferable(Transferable tr, TransferAction action) {
|
||||
public void handleTransferable(Transferable tr, TransferAction action) throws Exception {
|
||||
if (action == TransferAction.PUT) {
|
||||
clear();
|
||||
}
|
||||
|
@ -121,7 +122,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
|
|||
|
||||
|
||||
@Override
|
||||
protected void load(List<File> files) {
|
||||
protected void load(List<File> files) throws FileNotFoundException {
|
||||
if (containsOnly(files, LIST_FILES)) {
|
||||
loadListFiles(files);
|
||||
} else if (containsOnly(files, TORRENT_FILES)) {
|
||||
|
@ -144,28 +145,24 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
|
|||
}
|
||||
|
||||
|
||||
protected void loadListFiles(List<File> files) {
|
||||
try {
|
||||
List<MutableString> entries = new ArrayList<MutableString>();
|
||||
protected void loadListFiles(List<File> files) throws FileNotFoundException {
|
||||
List<MutableString> entries = new ArrayList<MutableString>();
|
||||
|
||||
for (File file : files) {
|
||||
Scanner scanner = new Scanner(file, "UTF-8").useDelimiter(LINE_SEPARATOR);
|
||||
|
||||
for (File file : files) {
|
||||
Scanner scanner = new Scanner(file, "UTF-8").useDelimiter(LINE_SEPARATOR);
|
||||
while (scanner.hasNext()) {
|
||||
String line = scanner.next();
|
||||
|
||||
while (scanner.hasNext()) {
|
||||
String line = scanner.next();
|
||||
|
||||
if (line.trim().length() > 0) {
|
||||
entries.add(new MutableString(line));
|
||||
}
|
||||
if (line.trim().length() > 0) {
|
||||
entries.add(new MutableString(line));
|
||||
}
|
||||
|
||||
scanner.close();
|
||||
}
|
||||
|
||||
submit(entries);
|
||||
} catch (IOException e) {
|
||||
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
|
||||
scanner.close();
|
||||
}
|
||||
|
||||
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.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.File;
|
||||
import java.util.EnumMap;
|
||||
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.ui.SwingWorkerPropertyChangeAdapter;
|
||||
|
||||
|
||||
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.root = root;
|
||||
this.task = computationTask;
|
||||
this.hashes = new EnumMap<HashType, String>(HashType.class);
|
||||
this.task = task;
|
||||
|
||||
// forward property change events
|
||||
task.addPropertyChangeListener(new SwingWorkerPropertyChangeAdapter() {
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
});
|
||||
task.addPropertyChangeListener(taskListener);
|
||||
}
|
||||
|
||||
|
||||
|
@ -77,11 +61,25 @@ class ChecksumCell {
|
|||
}
|
||||
|
||||
|
||||
public String getChecksum(HashType type) {
|
||||
if (hashes != null)
|
||||
return hashes.get(type);
|
||||
public String getChecksum(HashType hash) {
|
||||
return hashes.get(hash);
|
||||
}
|
||||
|
||||
|
||||
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() {
|
||||
if (hashes != null)
|
||||
return State.READY;
|
||||
if (error != null)
|
||||
return State.ERROR;
|
||||
|
||||
switch (task.getState()) {
|
||||
case PENDING:
|
||||
return State.PENDING;
|
||||
default:
|
||||
return State.PROGRESS;
|
||||
if (task != null) {
|
||||
switch (task.getState()) {
|
||||
case PENDING:
|
||||
return State.PENDING;
|
||||
default:
|
||||
return State.PROGRESS;
|
||||
}
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
return State.ERROR;
|
||||
}
|
||||
|
||||
return State.READY;
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
// clear property change support first
|
||||
// clear property change support
|
||||
for (PropertyChangeListener listener : pcs.getPropertyChangeListeners()) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
if (task != null) {
|
||||
task.removePropertyChangeListener(taskListener);
|
||||
task.cancel(true);
|
||||
}
|
||||
|
||||
|
@ -132,7 +134,42 @@ class ChecksumCell {
|
|||
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) {
|
||||
|
|
|
@ -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.PropertyChangeSupport;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -22,7 +23,7 @@ class ChecksumComputationService {
|
|||
|
||||
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 totalTaskCount = new AtomicInteger(0);
|
||||
|
@ -36,7 +37,12 @@ class ChecksumComputationService {
|
|||
public void reset() {
|
||||
synchronized (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);
|
||||
|
@ -50,7 +56,7 @@ class ChecksumComputationService {
|
|||
|
||||
|
||||
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));
|
||||
|
||||
synchronized (executors) {
|
||||
executors.add(this);
|
||||
if (executors.add(this) && executors.size() == 1) {
|
||||
totalTaskCount.set(0);
|
||||
completedTaskCount.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
prestartAllCoreThreads();
|
||||
|
@ -133,8 +142,22 @@ class ChecksumComputationService {
|
|||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
super.afterExecute(r, t);
|
||||
|
||||
completedTaskCount.incrementAndGet();
|
||||
fireTaskCountChanged();
|
||||
if (isValid()) {
|
||||
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.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
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 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.type = type;
|
||||
this.hashType = hashType;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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();
|
||||
|
||||
if (length > 0) {
|
||||
InputStream in = new FileInputStream(file);
|
||||
// open file
|
||||
InputStream in = new FileInputStream(file);
|
||||
|
||||
try {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
try {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
long position = 0;
|
||||
int len = 0;
|
||||
|
||||
while ((len = in.read(buffer)) >= 0) {
|
||||
position += len;
|
||||
|
||||
long position = 0;
|
||||
int len = 0;
|
||||
hash.update(buffer, 0, len);
|
||||
|
||||
while ((len = in.read(buffer)) >= 0) {
|
||||
position += len;
|
||||
|
||||
hash.update(buffer, 0, len);
|
||||
|
||||
// update progress
|
||||
setProgress((int) ((position * 100) / length));
|
||||
|
||||
// check abort status
|
||||
if (isCancelled()) {
|
||||
break;
|
||||
}
|
||||
// update progress
|
||||
setProgress((int) ((position * 100) / length));
|
||||
|
||||
// check abort status
|
||||
if (isCancelled() || Thread.interrupted()) {
|
||||
throw new CancellationException();
|
||||
}
|
||||
} 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.Set;
|
||||
|
||||
import javax.swing.event.SwingPropertyChangeSupport;
|
||||
|
||||
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) {
|
||||
return hashes.get(root);
|
||||
}
|
||||
|
||||
|
||||
public void put(ChecksumCell cell) {
|
||||
ChecksumCell old = hashes.put(cell.getRoot(), cell);
|
||||
|
||||
// dispose of old map entry
|
||||
if (old != null) {
|
||||
old.dispose();
|
||||
}
|
||||
|
||||
// update state immediately
|
||||
updateState();
|
||||
|
||||
// keep state up-to-date
|
||||
cell.addPropertyChangeListener(updateStateListener);
|
||||
public Collection<ChecksumCell> values() {
|
||||
return Collections.unmodifiableCollection(hashes.values());
|
||||
}
|
||||
|
||||
|
||||
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());
|
||||
|
||||
// keep state up-to-date
|
||||
cell.addPropertyChangeListener(updateStateListener);
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
// clear property change support
|
||||
for (PropertyChangeListener listener : pcs.getPropertyChangeListeners()) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
for (ChecksumCell cell : hashes.values()) {
|
||||
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);
|
||||
|
||||
if (checksum != null) {
|
||||
checksumSet.add(checksum);
|
||||
checksumSet.add(checksum.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,8 +169,22 @@ class ChecksumRow {
|
|||
private final PropertyChangeListener updateStateListener = new PropertyChangeListener() {
|
||||
|
||||
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;
|
||||
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtilities;
|
||||
import net.sourceforge.tuned.ui.TunedUtilities.DragDropRowTableUI;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtilities;
|
||||
|
||||
|
||||
class SfvTable extends JTable {
|
||||
class ChecksumTable extends JTable {
|
||||
|
||||
public SfvTable() {
|
||||
public ChecksumTable() {
|
||||
setFillsViewportHeight(true);
|
||||
setAutoCreateRowSorter(true);
|
||||
setAutoCreateColumnsFromModel(true);
|
||||
|
@ -23,12 +22,13 @@ class SfvTable extends JTable {
|
|||
|
||||
setRowHeight(20);
|
||||
|
||||
setDragEnabled(true);
|
||||
setUI(new DragDropRowTableUI());
|
||||
|
||||
// highlight CRC32 patterns in filenames in green and with smaller font-size
|
||||
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(FileBotUtilities.EMBEDDED_CHECKSUM_PATTERN, "#009900", "smaller"));
|
||||
setDefaultRenderer(ChecksumRow.State.class, new StateIconTableCellRenderer());
|
||||
setDefaultRenderer(ChecksumCell.class, new ChecksumTableCellRenderer());
|
||||
setDefaultRenderer(ChecksumRow.State.class, new StateIconCellRenderer());
|
||||
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
|
||||
public boolean canExport() {
|
||||
return model.getRowCount() > 0 && model.getChecksumColumns().size() > 0;
|
||||
return model.getRowCount() > 0 && defaultColumn() != null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void export(Formatter out) {
|
||||
export(out, model.getChecksumColumns().get(0));
|
||||
export(out, defaultColumn());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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) {
|
||||
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");
|
||||
|
||||
for (ChecksumRow row : model) {
|
||||
//TODO select hash type
|
||||
out.format("%s %s%n", row.getName(), row.getChecksum(column).getChecksum(HashType.CRC32));
|
||||
// print data
|
||||
VerificationFilePrinter printer = hashType.newPrinter(out);
|
||||
|
||||
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) {
|
||||
String name = "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (column != null)
|
||||
name = FileUtilities.getName(column);
|
||||
sb.append(FileUtilities.getName(column));
|
||||
else
|
||||
sb.append("name");
|
||||
|
||||
if (name.isEmpty())
|
||||
name = "name";
|
||||
|
||||
return name + ".sfv";
|
||||
return sb.append(".").append(model.getHashType().getExtension()).toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,29 +2,25 @@
|
|||
package net.sourceforge.filebot.ui.panel.sfv;
|
||||
|
||||
|
||||
import static javax.swing.event.TableModelEvent.UPDATE;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.File;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
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>() {
|
||||
|
||||
|
@ -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
|
||||
|
@ -64,19 +63,47 @@ class ChecksumTableModel extends AbstractTableModel implements Iterable<Checksum
|
|||
}
|
||||
|
||||
|
||||
public File getColumnRoot(int columnIndex) {
|
||||
return columns.get(columnIndex - 2);
|
||||
protected int getColumnIndex(ChecksumCell cell) {
|
||||
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
|
||||
public int getColumnCount() {
|
||||
return columns.size() + 2;
|
||||
// add checksum column offset
|
||||
return checksumColumns.size() + 2;
|
||||
}
|
||||
|
||||
|
||||
public List<File> getChecksumColumns() {
|
||||
return Collections.unmodifiableList(columns);
|
||||
protected int getRowIndex(ChecksumRow row) {
|
||||
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
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
ChecksumRow row = rows.get(rowIndex);
|
||||
|
@ -95,44 +140,81 @@ class ChecksumTableModel extends AbstractTableModel implements Iterable<Checksum
|
|||
return row.getState();
|
||||
case 1:
|
||||
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 Iterator<ChecksumRow> iterator() {
|
||||
return rows.iterator();
|
||||
}
|
||||
|
||||
|
||||
public void addAll(List<ChecksumCell> list) {
|
||||
int firstRow = getRowCount();
|
||||
public void addAll(Collection<ChecksumCell> values) {
|
||||
List<ChecksumCell> replacements = new ArrayList<ChecksumCell>();
|
||||
|
||||
for (ChecksumCell entry : list) {
|
||||
ChecksumRow row = rows.getByKey(entry.getName());
|
||||
int rowCount = getRowCount();
|
||||
int columnCount = getColumnCount();
|
||||
|
||||
for (ChecksumCell cell : values) {
|
||||
int rowIndex = getRowIndex(cell);
|
||||
|
||||
if (row == null) {
|
||||
row = new ChecksumRow(entry.getName());
|
||||
ChecksumRow row;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
entry.addPropertyChangeListener(progressListener);
|
||||
cell.addPropertyChangeListener(progressListener);
|
||||
|
||||
if (!columns.contains(entry.getRoot())) {
|
||||
columns.add(entry.getRoot());
|
||||
fireTableStructureChanged();
|
||||
if (!checksumColumns.contains(cell.getRoot())) {
|
||||
checksumColumns.add(cell.getRoot());
|
||||
}
|
||||
}
|
||||
|
||||
int lastRow = getRowCount() - 1;
|
||||
// fire table events
|
||||
if (columnCount != getColumnCount()) {
|
||||
// number of columns has changed
|
||||
fireTableStructureChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastRow >= firstRow) {
|
||||
fireTableRowsInserted(firstRow, lastRow);
|
||||
for (ChecksumCell replacement : replacements) {
|
||||
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() {
|
||||
columns.clear();
|
||||
checksumColumns.clear();
|
||||
rows.clear();
|
||||
|
||||
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 MutableTableModelEvent mutableUpdateEvent = new MutableTableModelEvent(ChecksumTableModel.this, UPDATE);
|
||||
|
||||
|
||||
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) {
|
||||
rows.get(index).updateState();
|
||||
fireTableChanged(mutableUpdateEvent.setRow(index));
|
||||
if (row >= 0 && column >= 0) {
|
||||
fireTableCellUpdated(row, column);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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> {
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
||||
enum HashType {
|
||||
|
||||
CRC32 {
|
||||
SFV {
|
||||
|
||||
@Override
|
||||
public Hash newInstance() {
|
||||
public Hash newHash() {
|
||||
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 {
|
||||
|
||||
@Override
|
||||
public Hash newInstance() {
|
||||
public Hash newHash() {
|
||||
return new MessageDigestHash("MD5");
|
||||
}
|
||||
},
|
||||
|
||||
SHA1 {
|
||||
|
||||
@Override
|
||||
public Hash newInstance() {
|
||||
public Hash newHash() {
|
||||
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>");
|
||||
|
||||
while (matcher.find()) {
|
||||
matcher.appendReplacement(htmlText, "<span style='font-size: " + cssFontSize + ";" + (!isSelected ? "color: " + cssColor + ";" : "") + "'>$0</span>");
|
||||
matcher.appendReplacement(htmlText, createReplacement(isSelected));
|
||||
}
|
||||
|
||||
matcher.appendTail(htmlText);
|
||||
|
@ -50,4 +50,21 @@ class HighlightPatternCellRenderer extends DefaultTableCellRenderer {
|
|||
|
||||
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;
|
||||
|
||||
|
||||
class StateIconTableCellRenderer extends DefaultTableCellRenderer {
|
||||
class StateIconCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
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.OK, ResourceManager.getIcon("status.ok"));
|
||||
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 java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JProgressBar;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.border.TitledBorder;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||
|
||||
|
||||
class TotalProgressPanel extends JComponent {
|
||||
|
||||
private int millisToSetVisible = 200;
|
||||
|
||||
private final JProgressBar progressBar = new JProgressBar(0, 0);
|
||||
|
||||
private final ChecksumComputationService computationService;
|
||||
private final int millisToSetVisible = 200;
|
||||
|
||||
|
||||
public TotalProgressPanel(ChecksumComputationService computationService) {
|
||||
this.computationService = computationService;
|
||||
setLayout(new MigLayout("insets 1px"));
|
||||
|
||||
setLayout(new MigLayout());
|
||||
setBorder(new TitledBorder("Total Progress"));
|
||||
|
||||
// invisible by default
|
||||
setVisible(false);
|
||||
|
||||
progressBar.setStringPainted(true);
|
||||
progressBar.setBorderPainted(false);
|
||||
progressBar.setString("");
|
||||
|
||||
setBorder(BorderFactory.createTitledBorder("Total Progress"));
|
||||
|
||||
add(progressBar, "growx");
|
||||
|
||||
|
@ -47,45 +42,82 @@ class TotalProgressPanel extends JComponent {
|
|||
|
||||
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) {
|
||||
final int completedTaskCount = computationService.getCompletedTaskCount();
|
||||
final int totalTaskCount = computationService.getTotalTaskCount();
|
||||
final int completedTaskCount = getComputationService(evt).getCompletedTaskCount();
|
||||
final int totalTaskCount = getComputationService(evt).getTotalTaskCount();
|
||||
|
||||
// invoke on EDT
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (completedTaskCount < totalTaskCount) {
|
||||
if (setVisibleTimer == null) {
|
||||
setVisibleTimer = TunedUtilities.invokeLater(millisToSetVisible, new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
setVisible(computationService.getTaskCount() > computationService.getCompletedTaskCount());
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (setVisibleTimer != null) {
|
||||
setVisibleTimer.stop();
|
||||
setVisibleTimer = null;
|
||||
}
|
||||
|
||||
// hide when not active
|
||||
setVisible(false);
|
||||
|
||||
if (completedTaskCount == totalTaskCount) {
|
||||
// delayed hide on reset, immediate hide on finish
|
||||
delayed.toggle(HIDE, totalTaskCount == 0 ? millisToSetVisible : 0, visibilityActionHandler);
|
||||
} else if (totalTaskCount != 0) {
|
||||
delayed.toggle(SHOW, millisToSetVisible, visibilityActionHandler);
|
||||
}
|
||||
|
||||
progressBar.setValue(completedTaskCount);
|
||||
progressBar.setMaximum(totalTaskCount);
|
||||
|
||||
progressBar.setString(String.format("%d / %d", completedTaskCount, totalTaskCount));
|
||||
if (totalTaskCount != 0) {
|
||||
progressBar.setValue(completedTaskCount);
|
||||
progressBar.setMaximum(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.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.event.SwingPropertyChangeSupport;
|
||||
|
||||
|
||||
public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferablePolicy {
|
||||
|
||||
public static final String LOADING_PROPERTY = "loading";
|
||||
|
||||
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 ThreadLocal<BackgroundWorker> threadLocalWorker = new ThreadLocal<BackgroundWorker>();
|
||||
|
||||
private final List<BackgroundWorker> workers = new ArrayList<BackgroundWorker>(2);
|
||||
|
||||
|
||||
@Override
|
||||
public void handleTransferable(Transferable tr, TransferAction action) {
|
||||
public void handleTransferable(Transferable tr, TransferAction action) throws Exception {
|
||||
List<File> files = getFilesFromTransferable(tr);
|
||||
|
||||
if (action != TransferAction.ADD)
|
||||
if (action != TransferAction.ADD) {
|
||||
clear();
|
||||
}
|
||||
|
||||
prepare(files);
|
||||
|
||||
// create and start worker
|
||||
new BackgroundWorker(files).execute();
|
||||
}
|
||||
|
||||
|
||||
protected void prepare(List<File> files) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void clear() {
|
||||
// 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(Exception e);
|
||||
protected abstract void process(Exception exception);
|
||||
|
||||
|
||||
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
|
||||
protected Object doInBackground() {
|
||||
protected Object doInBackground() throws Exception {
|
||||
// associate this worker with the current (background) thread
|
||||
threadLocalWorker.set(this);
|
||||
|
||||
|
@ -121,13 +148,6 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
|
|||
|
||||
@Override
|
||||
protected void done() {
|
||||
// unregister worker
|
||||
synchronized (workers) {
|
||||
if (workers.remove(this) && workers.isEmpty()) {
|
||||
swingPropertyChangeSupport.firePropertyChange(LOADING_PROPERTY, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
// check for exception
|
||||
|
@ -136,27 +156,17 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
|
|||
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) {
|
||||
|
||||
@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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
protected final PropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport(this, true);
|
||||
|
||||
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
|
|
|
@ -4,9 +4,6 @@ package net.sourceforge.filebot.ui.transfer;
|
|||
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JTable;
|
||||
|
@ -16,56 +13,58 @@ import javax.swing.tree.TreePath;
|
|||
|
||||
public class DefaultClipboardHandler implements ClipboardHandler {
|
||||
|
||||
protected final String newLine = System.getProperty("line.separator");
|
||||
|
||||
|
||||
@Override
|
||||
public void exportToClipboard(JComponent component, Clipboard clip, int action) throws IllegalStateException {
|
||||
StringWriter buffer = new StringWriter();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (component instanceof JList) {
|
||||
export(new PrintWriter(buffer), (JList) component);
|
||||
export(sb, (JList) component);
|
||||
} else if (component instanceof JTree) {
|
||||
export(new PrintWriter(buffer), (JTree) component);
|
||||
export(sb, (JTree) component);
|
||||
} 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()) {
|
||||
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()) {
|
||||
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 columnIndex = 0; columnIndex < table.getColumnCount(); columnIndex++) {
|
||||
out.print(valueToString(table.getModel().getValueAt(row, columnIndex)));
|
||||
int modelRow = table.getRowSorter().convertRowIndexToModel(row);
|
||||
|
||||
for (int column = 0; column < table.getColumnCount(); column++) {
|
||||
Object value = table.getModel().getValueAt(modelRow, column);
|
||||
|
||||
if (columnIndex < table.getColumnCount() - 1)
|
||||
out.print("\t");
|
||||
if (value != null) {
|
||||
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.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
@ -27,7 +26,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
|
|||
|
||||
|
||||
@Override
|
||||
public boolean accept(Transferable tr) {
|
||||
public boolean accept(Transferable tr) throws Exception {
|
||||
List<File> files = getFilesFromTransferable(tr);
|
||||
|
||||
if (files.isEmpty())
|
||||
|
@ -38,48 +37,40 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
|
|||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List<File> getFilesFromTransferable(Transferable tr) {
|
||||
try {
|
||||
if (tr.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
||||
// file list flavor
|
||||
return (List<File>) tr.getTransferData(DataFlavor.javaFileListFlavor);
|
||||
} else if (tr.isDataFlavorSupported(FileTransferable.uriListFlavor)) {
|
||||
// file URI list flavor
|
||||
String transferData = (String) tr.getTransferData(FileTransferable.uriListFlavor);
|
||||
protected List<File> getFilesFromTransferable(Transferable tr) throws Exception {
|
||||
if (tr.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
||||
// file list flavor
|
||||
return (List<File>) tr.getTransferData(DataFlavor.javaFileListFlavor);
|
||||
} else if (tr.isDataFlavorSupported(FileTransferable.uriListFlavor)) {
|
||||
// file URI list flavor
|
||||
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);
|
||||
|
||||
ArrayList<File> files = new ArrayList<File>();
|
||||
|
||||
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);
|
||||
}
|
||||
if (uri.startsWith("#")) {
|
||||
// the line is a comment (as per RFC 2483)
|
||||
continue;
|
||||
}
|
||||
|
||||
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
|
||||
throw new RuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
// should not happen
|
||||
throw new RuntimeException(e);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
|
@ -87,7 +78,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
|
|||
|
||||
|
||||
@Override
|
||||
public void handleTransferable(Transferable tr, TransferAction action) {
|
||||
public void handleTransferable(Transferable tr, TransferAction action) throws Exception {
|
||||
List<File> files = getFilesFromTransferable(tr);
|
||||
|
||||
if (action == TransferAction.PUT) {
|
||||
|
@ -101,7 +92,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
|
|||
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();
|
||||
|
|
|
@ -3,6 +3,8 @@ package net.sourceforge.filebot.ui.transfer;
|
|||
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
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
|
||||
TransferablePolicy transferablePolicy = (TransferablePolicy) getValue(TRANSFERABLE_POLICY);
|
||||
|
||||
|
@ -45,10 +47,17 @@ public class LoadAction extends AbstractAction {
|
|||
TransferAction action = TransferAction.PUT;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (transferablePolicy.accept(transferable))
|
||||
transferablePolicy.handleTransferable(transferable, action);
|
||||
try {
|
||||
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 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) {
|
||||
|
||||
if (support.isDrop())
|
||||
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
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import java.io.File;
|
|||
import javax.swing.filechooser.FileFilter;
|
||||
|
||||
|
||||
|
||||
public class TransferablePolicyFileFilter extends FileFilter {
|
||||
|
||||
private final TransferablePolicy transferablePolicy;
|
||||
|
@ -23,7 +22,11 @@ public class TransferablePolicyFileFilter extends FileFilter {
|
|||
if (f.isDirectory())
|
||||
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) {
|
||||
Thread thread = new Thread(group, r, String.format("%s-thread-%d", group.getName(), threadNumber.incrementAndGet()));
|
||||
|
||||
thread.setDaemon(daemon);
|
||||
thread.setPriority(priority);
|
||||
if (daemon != thread.isDaemon())
|
||||
thread.setDaemon(daemon);
|
||||
|
||||
if (priority != thread.getPriority())
|
||||
thread.setPriority(priority);
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ public final class ExceptionUtilities {
|
|||
String message = t.getMessage();
|
||||
|
||||
if (message == null || message.isEmpty()) {
|
||||
return t.toString();
|
||||
message = t.toString().replaceAll(t.getClass().getName(), t.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
return message;
|
||||
|
|
|
@ -6,6 +6,8 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
|
@ -73,7 +75,11 @@ public class MessageBus {
|
|||
|
||||
private void publishDirect(String topic, Object... messages) {
|
||||
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 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
|
||||
public void setLabel(String label) {
|
||||
headerLabel.setText(label);
|
||||
|
|
|
@ -93,7 +93,6 @@ public final class TunedUtilities {
|
|||
public void actionPerformed(ActionEvent e) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
timer.setRepeats(false);
|
||||
|
|
|
@ -11,7 +11,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
|||
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses( { SimilarityTestSuite.class, WebTestSuite.class, ArgumentBeanTest.class })
|
||||
@SuiteClasses( { SimilarityTestSuite.class, WebTestSuite.class, MiscSuite.class })
|
||||
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