* major refactoring of Checksum* Classes (TableModel, ComputationTask, ComputationService)
* SfvTransferablePolicy create one dedicated ComputationTask executor for each drop * ComputationTask always computes CRC32, MD5 and SHA-1 * changed TextFileExportHandler to use Formatter instead of PrintWriter * renamed *Util to *Utilities * update to GlazedLists 1.8
This commit is contained in:
parent
684a7512bc
commit
5674173417
Binary file not shown.
|
@ -7,7 +7,7 @@ import java.util.Map;
|
||||||
import java.util.prefs.BackingStoreException;
|
import java.util.prefs.BackingStoreException;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
import net.sourceforge.tuned.ExceptionUtil;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
import net.sourceforge.tuned.PreferencesList;
|
import net.sourceforge.tuned.PreferencesList;
|
||||||
import net.sourceforge.tuned.PreferencesMap;
|
import net.sourceforge.tuned.PreferencesMap;
|
||||||
import net.sourceforge.tuned.PreferencesMap.Adapter;
|
import net.sourceforge.tuned.PreferencesMap.Adapter;
|
||||||
|
@ -96,7 +96,7 @@ public final class Settings {
|
||||||
// remove entries
|
// remove entries
|
||||||
prefs.clear();
|
prefs.clear();
|
||||||
} catch (BackingStoreException e) {
|
} catch (BackingStoreException e) {
|
||||||
throw ExceptionUtil.asRuntimeException(e);
|
throw ExceptionUtilities.asRuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import javax.swing.SwingWorker;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
import net.sourceforge.filebot.web.SearchResult;
|
import net.sourceforge.filebot.web.SearchResult;
|
||||||
import net.sourceforge.tuned.ExceptionUtil;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
import net.sourceforge.tuned.ui.LabelProvider;
|
import net.sourceforge.tuned.ui.LabelProvider;
|
||||||
import net.sourceforge.tuned.ui.SelectButtonTextField;
|
import net.sourceforge.tuned.ui.SelectButtonTextField;
|
||||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||||
|
@ -184,7 +184,7 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
tab.close();
|
tab.close();
|
||||||
|
|
||||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtil.getRootCause(e).getMessage(), e);
|
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCause(e).getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -240,7 +240,7 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
tab.close();
|
tab.close();
|
||||||
|
|
||||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtil.getRootCause(e).getMessage(), e);
|
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCause(e).getMessage(), e);
|
||||||
} finally {
|
} finally {
|
||||||
tab.setLoading(false);
|
tab.setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
package net.sourceforge.filebot.ui;
|
package net.sourceforge.filebot.ui;
|
||||||
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.util.Formatter;
|
||||||
|
|
||||||
import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
|
import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@ public class FileBotListExportHandler extends TextFileExportHandler {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void export(PrintWriter out) {
|
public void export(Formatter out) {
|
||||||
for (Object entry : list.getModel()) {
|
for (Object entry : list.getModel()) {
|
||||||
out.println(entry);
|
out.format("%s%n", entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import javax.swing.SwingWorker;
|
||||||
|
|
||||||
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FileNode;
|
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FileNode;
|
||||||
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode;
|
import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode;
|
||||||
import net.sourceforge.tuned.ExceptionUtil;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ abstract class Tool<M> extends JComponent {
|
||||||
try {
|
try {
|
||||||
setModel(get());
|
setModel(get());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (ExceptionUtil.getRootCause(e) instanceof ConcurrentModificationException) {
|
if (ExceptionUtilities.getRootCause(e) instanceof ConcurrentModificationException) {
|
||||||
// if it happens, it is supposed to
|
// if it happens, it is supposed to
|
||||||
} else {
|
} else {
|
||||||
// should not happen
|
// should not happen
|
||||||
|
|
|
@ -49,14 +49,19 @@ class AutoFetchEpisodeListMatcher extends SwingWorker<List<Match<File, Episode>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Collection<File> remainingFiles() {
|
public List<File> remainingFiles() {
|
||||||
return Collections.unmodifiableCollection(files);
|
return Collections.unmodifiableList(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Collection<String> detectSeriesNames(Collection<File> files) {
|
protected Collection<String> detectSeriesNames(Collection<File> files) {
|
||||||
// detect series name(s) from files
|
// detect series name(s) from files
|
||||||
return new SeriesNameMatcher().matchAll(files.toArray(new File[0]));
|
Collection<String> names = new SeriesNameMatcher().matchAll(files.toArray(new File[0]));
|
||||||
|
|
||||||
|
if (names.isEmpty())
|
||||||
|
throw new IllegalArgumentException("Cannot auto-detect series name.");
|
||||||
|
|
||||||
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,9 +84,6 @@ class AutoFetchEpisodeListMatcher extends SwingWorker<List<Match<File, Episode>>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tasks.isEmpty())
|
|
||||||
throw new IllegalArgumentException("Failed to auto-detect series name.");
|
|
||||||
|
|
||||||
// fetch episode lists concurrently
|
// fetch episode lists concurrently
|
||||||
List<Episode> episodes = new ArrayList<Episode>();
|
List<Episode> episodes = new ArrayList<Episode>();
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(tasks.size());
|
ExecutorService executor = Executors.newFixedThreadPool(tasks.size());
|
||||||
|
|
|
@ -8,13 +8,12 @@ import java.util.Collection;
|
||||||
import net.sourceforge.filebot.similarity.Match;
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
import ca.odell.glazedlists.BasicEventList;
|
import ca.odell.glazedlists.BasicEventList;
|
||||||
import ca.odell.glazedlists.EventList;
|
import ca.odell.glazedlists.EventList;
|
||||||
import ca.odell.glazedlists.GlazedLists;
|
|
||||||
|
|
||||||
|
|
||||||
class RenameModel {
|
class RenameModel {
|
||||||
|
|
||||||
private final EventList<Object> names = GlazedLists.threadSafeList(new BasicEventList<Object>());
|
private final EventList<Object> names = new BasicEventList<Object>();
|
||||||
private final EventList<FileEntry> files = GlazedLists.threadSafeList(new BasicEventList<FileEntry>());
|
private final EventList<FileEntry> files = new BasicEventList<FileEntry>();
|
||||||
|
|
||||||
|
|
||||||
public EventList<Object> names() {
|
public EventList<Object> names() {
|
||||||
|
|
|
@ -31,7 +31,7 @@ import net.sourceforge.filebot.web.Episode;
|
||||||
import net.sourceforge.filebot.web.EpisodeListClient;
|
import net.sourceforge.filebot.web.EpisodeListClient;
|
||||||
import net.sourceforge.filebot.web.TVRageClient;
|
import net.sourceforge.filebot.web.TVRageClient;
|
||||||
import net.sourceforge.filebot.web.TheTVDBClient;
|
import net.sourceforge.filebot.web.TheTVDBClient;
|
||||||
import net.sourceforge.tuned.ExceptionUtil;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
import net.sourceforge.tuned.ui.ActionPopup;
|
import net.sourceforge.tuned.ui.ActionPopup;
|
||||||
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
||||||
import ca.odell.glazedlists.event.ListEvent;
|
import ca.odell.glazedlists.event.ListEvent;
|
||||||
|
@ -215,7 +215,7 @@ public class RenamePanel extends FileBotPanel {
|
||||||
model.names().addAll(names);
|
model.names().addAll(names);
|
||||||
model.files().addAll(files);
|
model.files().addAll(files);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtil.getRootCause(e).getMessage(), e);
|
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCause(e).getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
|
||||||
|
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
|
||||||
import java.beans.PropertyChangeListener;
|
|
||||||
import java.beans.PropertyChangeSupport;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
|
||||||
|
|
||||||
|
|
||||||
public class Checksum {
|
|
||||||
|
|
||||||
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
|
|
||||||
|
|
||||||
public static final String STATE_PROPERTY = "state";
|
|
||||||
public static final String PROGRESS_PROPERTY = "progress";
|
|
||||||
|
|
||||||
private Long checksum = null;
|
|
||||||
private State state = State.PENDING;
|
|
||||||
private ChecksumComputationTask computationTask = null;
|
|
||||||
private String errorMessage = null;
|
|
||||||
|
|
||||||
|
|
||||||
public static enum State {
|
|
||||||
PENDING,
|
|
||||||
INPROGRESS,
|
|
||||||
READY,
|
|
||||||
ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Checksum(long checksum) {
|
|
||||||
setChecksum(checksum);
|
|
||||||
setState(State.READY);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Checksum(String checksumString) {
|
|
||||||
this(Long.parseLong(checksumString, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected Checksum(ChecksumComputationTask computationTask) {
|
|
||||||
this.computationTask = computationTask;
|
|
||||||
this.computationTask.addPropertyChangeListener(new ComputationTaskPropertyChangeListener());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getChecksumString() {
|
|
||||||
return String.format("%08x", checksum).toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Long getChecksum() {
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized void setChecksum(Long checksum) {
|
|
||||||
this.checksum = checksum;
|
|
||||||
setState(State.READY);
|
|
||||||
|
|
||||||
computationTask = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized void setChecksumError(Exception exception) {
|
|
||||||
// get root cause
|
|
||||||
Throwable cause = exception;
|
|
||||||
|
|
||||||
while (cause.getCause() != null)
|
|
||||||
cause = cause.getCause();
|
|
||||||
|
|
||||||
errorMessage = cause.getMessage();
|
|
||||||
setState(State.ERROR);
|
|
||||||
|
|
||||||
computationTask = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public State getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void setState(State state) {
|
|
||||||
this.state = state;
|
|
||||||
propertyChangeSupport.firePropertyChange(STATE_PROPERTY, null, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized Integer getProgress() {
|
|
||||||
if (state == State.INPROGRESS)
|
|
||||||
return computationTask.getProgress();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getErrorMessage() {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized void cancelComputationTask() {
|
|
||||||
if (computationTask == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
computationTask.cancel(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class ComputationTaskPropertyChangeListener extends SwingWorkerPropertyChangeAdapter {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void progress(PropertyChangeEvent evt) {
|
|
||||||
propertyChangeSupport.firePropertyChange(PROGRESS_PROPERTY, null, evt.getNewValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void started(PropertyChangeEvent evt) {
|
|
||||||
setState(State.INPROGRESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void done(PropertyChangeEvent evt) {
|
|
||||||
try {
|
|
||||||
ChecksumComputationTask task = (ChecksumComputationTask) evt.getSource();
|
|
||||||
|
|
||||||
if (!task.isCancelled()) {
|
|
||||||
setChecksum(task.get());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// might happen if file system is corrupt (e.g. CRC errors)
|
|
||||||
setChecksumError(e);
|
|
||||||
Logger.getLogger("global").log(Level.WARNING, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
|
||||||
propertyChangeSupport.addPropertyChangeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
|
||||||
propertyChangeSupport.removePropertyChangeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
if (state == State.READY)
|
|
||||||
return getChecksumString();
|
|
||||||
|
|
||||||
if (state == State.ERROR)
|
|
||||||
return getErrorMessage();
|
|
||||||
|
|
||||||
return state.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
|
||||||
|
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.Map;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
||||||
|
|
||||||
|
|
||||||
|
public class ChecksumCell {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final File root;
|
||||||
|
|
||||||
|
private Map<HashType, String> hashes;
|
||||||
|
private ChecksumComputationTask task;
|
||||||
|
private Throwable error;
|
||||||
|
|
||||||
|
|
||||||
|
public static enum State {
|
||||||
|
PENDING,
|
||||||
|
PROGRESS,
|
||||||
|
READY,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ChecksumCell(String name, File root, Map<HashType, String> hashes) {
|
||||||
|
this.name = name;
|
||||||
|
this.root = root;
|
||||||
|
this.hashes = hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ChecksumCell(String name, File root, ChecksumComputationTask computationTask) {
|
||||||
|
this.name = name;
|
||||||
|
this.root = root;
|
||||||
|
this.task = computationTask;
|
||||||
|
|
||||||
|
// forward property change events
|
||||||
|
task.addPropertyChangeListener(new SwingWorkerPropertyChangeAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
super.propertyChange(evt);
|
||||||
|
|
||||||
|
propertyChangeSupport.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public File getRoot() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getChecksum(HashType type) {
|
||||||
|
if (hashes != null)
|
||||||
|
return hashes.get(type);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ChecksumComputationTask getTask() {
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Throwable getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
if (task != null) {
|
||||||
|
task.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
hashes = null;
|
||||||
|
error = null;
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s %s", name, hashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
|
||||||
|
|
||||||
|
|
||||||
|
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
propertyChangeSupport.addPropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
propertyChangeSupport.removePropertyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,194 +2,130 @@
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import static java.lang.Math.log10;
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.beans.PropertyChangeSupport;
|
import java.beans.PropertyChangeSupport;
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.ConcurrentModificationException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
|
|
||||||
import net.sourceforge.tuned.DefaultThreadFactory;
|
import net.sourceforge.tuned.DefaultThreadFactory;
|
||||||
|
|
||||||
|
|
||||||
public class ChecksumComputationService {
|
public class ChecksumComputationService {
|
||||||
|
|
||||||
public static final String ACTIVE_PROPERTY = "active";
|
public static final String TASK_COUNT_PROPERTY = "taskCount";
|
||||||
public static final String REMAINING_TASK_COUNT_PROPERTY = "remainingTaskCount";
|
|
||||||
|
|
||||||
private final Map<File, ChecksumComputationExecutor> executors = new HashMap<File, ChecksumComputationExecutor>();
|
private final List<ThreadPoolExecutor> executors = new ArrayList<ThreadPoolExecutor>();
|
||||||
|
|
||||||
private final AtomicInteger activeSessionTaskCount = new AtomicInteger(0);
|
private final AtomicInteger completedTaskCount = new AtomicInteger(0);
|
||||||
private final AtomicInteger remainingTaskCount = new AtomicInteger(0);
|
private final AtomicInteger totalTaskCount = new AtomicInteger(0);
|
||||||
|
|
||||||
private final ThreadFactory threadFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Property change events will be fired on the event dispatch thread
|
|
||||||
*/
|
|
||||||
private final SwingWorkerPropertyChangeSupport propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this);
|
|
||||||
|
|
||||||
|
|
||||||
public ChecksumComputationService() {
|
public ExecutorService newExecutor() {
|
||||||
this(new DefaultThreadFactory("ChecksumComputationPool", Thread.MIN_PRIORITY));
|
return new ChecksumComputationExecutor();
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public ChecksumComputationService(ThreadFactory threadFactory) {
|
|
||||||
this.threadFactory = threadFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Checksum schedule(File file, File workerQueue) {
|
|
||||||
ChecksumComputationTask task = new ChecksumComputationTask(file);
|
|
||||||
Checksum checksum = new Checksum(task);
|
|
||||||
|
|
||||||
getExecutor(workerQueue).execute(task);
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
deactivate(true);
|
synchronized (executors) {
|
||||||
}
|
for (ExecutorService executor : executors) {
|
||||||
|
|
||||||
|
|
||||||
private synchronized void deactivate(boolean shutdownNow) {
|
|
||||||
for (ChecksumComputationExecutor executor : executors.values()) {
|
|
||||||
if (shutdownNow)
|
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
else
|
}
|
||||||
executor.shutdown();
|
|
||||||
|
totalTaskCount.set(0);
|
||||||
|
completedTaskCount.set(0);
|
||||||
|
|
||||||
|
executors.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
executors.clear();
|
fireTaskCountChanged();
|
||||||
|
|
||||||
activeSessionTaskCount.set(0);
|
|
||||||
remainingTaskCount.set(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isActive() {
|
public int getTaskCount() {
|
||||||
return activeSessionTaskCount.get() > 0;
|
return getTotalTaskCount() - getCompletedTaskCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getRemainingTaskCount() {
|
public int getTotalTaskCount() {
|
||||||
return remainingTaskCount.get();
|
return totalTaskCount.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getActiveSessionTaskCount() {
|
public int getCompletedTaskCount() {
|
||||||
return activeSessionTaskCount.get();
|
return completedTaskCount.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public synchronized void purge() {
|
public void purge() {
|
||||||
for (ChecksumComputationExecutor executor : executors.values()) {
|
synchronized (executors) {
|
||||||
executor.purge();
|
for (ThreadPoolExecutor executor : executors) {
|
||||||
|
executor.purge();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private synchronized ChecksumComputationExecutor getExecutor(File workerQueue) {
|
|
||||||
ChecksumComputationExecutor executor = executors.get(workerQueue);
|
|
||||||
|
|
||||||
if (executor == null) {
|
|
||||||
executor = new ChecksumComputationExecutor();
|
|
||||||
executors.put(workerQueue, executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
return executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class ChecksumComputationExecutor extends ThreadPoolExecutor {
|
private class ChecksumComputationExecutor extends ThreadPoolExecutor {
|
||||||
|
|
||||||
private static final int MINIMUM_POOL_SIZE = 1;
|
|
||||||
|
|
||||||
|
|
||||||
public ChecksumComputationExecutor() {
|
public ChecksumComputationExecutor() {
|
||||||
super(MINIMUM_POOL_SIZE, MINIMUM_POOL_SIZE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory);
|
super(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new DefaultThreadFactory("ChecksumComputationPool", Thread.MIN_PRIORITY));
|
||||||
|
|
||||||
|
synchronized (executors) {
|
||||||
|
executors.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
prestartAllCoreThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void adjustPoolSize() {
|
protected int getPreferredPoolSize() {
|
||||||
// for a few files, use one thread
|
// for a few files, use one thread
|
||||||
// for lots of files, use multiple threads
|
// for lots of files, use multiple threads
|
||||||
// e.g 50 files ~ 1 thread, 1000 files ~ 3 threads, 40000 files ~ 5 threads
|
// e.g 50 files ~ 1 thread, 200 files ~ 2 threads, 1000 files ~ 3 threads, 40000 files ~ 5 threads
|
||||||
int preferredPoolSize = (int) Math.max(Math.log10(getQueue().size() / 10), MINIMUM_POOL_SIZE);
|
return max((int) log10(getQueue().size()), 1);
|
||||||
|
|
||||||
if (getCorePoolSize() != preferredPoolSize) {
|
|
||||||
setCorePoolSize(preferredPoolSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(Runnable command) {
|
public void execute(Runnable command) {
|
||||||
if (activeSessionTaskCount.getAndIncrement() <= 0) {
|
int preferredPoolSize = getPreferredPoolSize();
|
||||||
setActive(true);
|
|
||||||
|
if (getCorePoolSize() < preferredPoolSize) {
|
||||||
|
setCorePoolSize(preferredPoolSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.execute(command);
|
synchronized (this) {
|
||||||
|
super.execute(command);
|
||||||
|
}
|
||||||
|
|
||||||
adjustPoolSize();
|
totalTaskCount.incrementAndGet();
|
||||||
|
fireTaskCountChanged();
|
||||||
remainingTaskCount.incrementAndGet();
|
|
||||||
fireRemainingTaskCountChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void purge() {
|
public void purge() {
|
||||||
try {
|
int delta = 0;
|
||||||
List<ChecksumComputationTask> cancelledTasks = new ArrayList<ChecksumComputationTask>();
|
|
||||||
|
|
||||||
for (Runnable entry : getQueue()) {
|
|
||||||
ChecksumComputationTask task = (ChecksumComputationTask) entry;
|
|
||||||
|
|
||||||
if (task.isCancelled()) {
|
|
||||||
cancelledTasks.add(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ChecksumComputationTask task : cancelledTasks) {
|
|
||||||
remove(task);
|
|
||||||
}
|
|
||||||
} catch (ConcurrentModificationException e) {
|
|
||||||
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean remove(Runnable task) {
|
|
||||||
boolean success = super.remove(task);
|
|
||||||
|
|
||||||
if (success) {
|
synchronized (this) {
|
||||||
activeSessionTaskCount.decrementAndGet();
|
delta += getQueue().size();
|
||||||
|
super.purge();
|
||||||
if (remainingTaskCount.decrementAndGet() <= 0) {
|
delta -= getQueue().size();
|
||||||
setActive(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fireRemainingTaskCountChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
if (delta > 0) {
|
||||||
|
// subtract removed tasks from task count
|
||||||
|
totalTaskCount.addAndGet(-delta);
|
||||||
|
fireTaskCountChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -197,54 +133,35 @@ public class ChecksumComputationService {
|
||||||
protected void afterExecute(Runnable r, Throwable t) {
|
protected void afterExecute(Runnable r, Throwable t) {
|
||||||
super.afterExecute(r, t);
|
super.afterExecute(r, t);
|
||||||
|
|
||||||
if (remainingTaskCount.decrementAndGet() <= 0) {
|
completedTaskCount.incrementAndGet();
|
||||||
deactivate(false);
|
fireTaskCountChanged();
|
||||||
setActive(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fireRemainingTaskCountChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void setActive(boolean active) {
|
|
||||||
propertyChangeSupport.firePropertyChange(ACTIVE_PROPERTY, null, active);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void fireRemainingTaskCountChange() {
|
|
||||||
propertyChangeSupport.firePropertyChange(REMAINING_TASK_COUNT_PROPERTY, null, getRemainingTaskCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
|
||||||
propertyChangeSupport.addPropertyChangeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
|
||||||
propertyChangeSupport.removePropertyChangeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static class SwingWorkerPropertyChangeSupport extends PropertyChangeSupport {
|
|
||||||
|
|
||||||
public SwingWorkerPropertyChangeSupport(Object sourceBean) {
|
|
||||||
super(sourceBean);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void firePropertyChange(final PropertyChangeEvent evt) {
|
protected void terminated() {
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
synchronized (executors) {
|
||||||
|
executors.remove(this);
|
||||||
@Override
|
}
|
||||||
public void run() {
|
|
||||||
SwingWorkerPropertyChangeSupport.super.firePropertyChange(evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
|
||||||
|
|
||||||
|
|
||||||
|
private void fireTaskCountChanged() {
|
||||||
|
propertyChangeSupport.firePropertyChange(TASK_COUNT_PROPERTY, null, getTaskCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||||
|
propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||||
|
propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.util.zip.CRC32;
|
import java.io.InputStream;
|
||||||
import java.util.zip.CheckedInputStream;
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
|
|
||||||
public class ChecksumComputationTask extends SwingWorker<Long, Void> {
|
class ChecksumComputationTask extends SwingWorker<Map<HashType, String>, Void> {
|
||||||
|
|
||||||
private static final int BUFFER_SIZE = 32 * 1024;
|
private static final int BUFFER_SIZE = 32 * 1024;
|
||||||
|
|
||||||
|
@ -23,37 +24,56 @@ public class ChecksumComputationTask extends SwingWorker<Long, Void> {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Long doInBackground() throws Exception {
|
protected Map<HashType, String> doInBackground() throws Exception {
|
||||||
CheckedInputStream cis = new CheckedInputStream(new FileInputStream(file), new CRC32());
|
Map<HashType, Hash> hashes = new EnumMap<HashType, Hash>(HashType.class);
|
||||||
|
|
||||||
|
for (HashType type : HashType.values()) {
|
||||||
|
hashes.put(type, type.newInstance());
|
||||||
|
}
|
||||||
|
|
||||||
long length = file.length();
|
long length = file.length();
|
||||||
|
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
long done = 0;
|
InputStream in = new FileInputStream(file);
|
||||||
|
|
||||||
byte[] buffer = new byte[BUFFER_SIZE];
|
try {
|
||||||
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
int bytesRead = 0;
|
|
||||||
|
long position = 0;
|
||||||
while ((bytesRead = cis.read(buffer)) >= 0) {
|
int len = 0;
|
||||||
if (isCancelled() || Thread.currentThread().isInterrupted())
|
|
||||||
break;
|
|
||||||
|
|
||||||
done += bytesRead;
|
while ((len = in.read(buffer)) >= 0) {
|
||||||
|
position += len;
|
||||||
int progress = (int) ((done * 100) / length);
|
|
||||||
setProgress(progress);
|
for (Hash hash : hashes.values()) {
|
||||||
|
hash.update(buffer, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update progress
|
||||||
|
setProgress((int) ((position * 100) / length));
|
||||||
|
|
||||||
|
// check abort status
|
||||||
|
if (isCancelled() || Thread.interrupted()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cis.close();
|
return digest(hashes);
|
||||||
|
|
||||||
return cis.getChecksum().getValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
private Map<HashType, String> digest(Map<HashType, Hash> hashes) {
|
||||||
public String toString() {
|
Map<HashType, String> results = new EnumMap<HashType, String>(HashType.class);
|
||||||
return String.format("%s (%s)", getClass().getSimpleName(), file.getName());
|
|
||||||
|
for (Entry<HashType, Hash> entry : hashes.entrySet()) {
|
||||||
|
results.put(entry.getKey(), entry.getValue().digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.zip.Checksum;
|
||||||
|
|
||||||
|
|
||||||
|
class ChecksumHash implements Hash {
|
||||||
|
|
||||||
|
private final Checksum checksum;
|
||||||
|
|
||||||
|
|
||||||
|
public ChecksumHash(Checksum checksum) {
|
||||||
|
this.checksum = checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(byte[] bytes, int off, int len) {
|
||||||
|
checksum.update(bytes, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String digest() {
|
||||||
|
return String.format("%08X", checksum.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,8 +4,12 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileBotUtilities;
|
import net.sourceforge.filebot.FileBotUtilities;
|
||||||
|
|
||||||
|
@ -14,48 +18,26 @@ public class ChecksumRow {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
private HashMap<File, Checksum> checksumMap = new HashMap<File, Checksum>();
|
private Map<File, ChecksumCell> hashes = new HashMap<File, ChecksumCell>(4);
|
||||||
|
private State state = State.UNKNOWN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checksum that is embedded in the file name (e.g. Test[49A93C5F].txt)
|
* Checksum that is embedded in the file name (e.g. Test[49A93C5F].txt)
|
||||||
*/
|
*/
|
||||||
private final Long embeddedChecksum;
|
private String embeddedChecksum;
|
||||||
|
|
||||||
|
|
||||||
public static enum State {
|
public static enum State {
|
||||||
|
UNKNOWN,
|
||||||
OK,
|
OK,
|
||||||
WARNING,
|
WARNING,
|
||||||
ERROR,
|
ERROR
|
||||||
UNKNOWN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ChecksumRow(String name) {
|
public ChecksumRow(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.embeddedChecksum = getEmbeddedChecksum(name);
|
this.embeddedChecksum = FileBotUtilities.getEmbeddedChecksum(name);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to parse a CRC32 checksum from the given file name. The checksum is assumed to be in
|
|
||||||
* brackets.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* e.g.
|
|
||||||
* Test[49A93C5F].txt
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param file name that contains a checksum
|
|
||||||
* @return the checksum or null, if parameter did not contain a checksum
|
|
||||||
*/
|
|
||||||
private static Long getEmbeddedChecksum(String name) {
|
|
||||||
// look for a checksum pattern like [49A93C5F]
|
|
||||||
String match = FileBotUtilities.getEmbeddedChecksum(name);
|
|
||||||
|
|
||||||
if (match != null)
|
|
||||||
return Long.parseLong(match, 16);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,50 +47,91 @@ public class ChecksumRow {
|
||||||
|
|
||||||
|
|
||||||
public State getState() {
|
public State getState() {
|
||||||
HashSet<Long> checksums = new HashSet<Long>();
|
return state;
|
||||||
|
}
|
||||||
for (Checksum checksum : getChecksums()) {
|
|
||||||
if (checksum.getState() == Checksum.State.READY) {
|
|
||||||
checksums.add(checksum.getChecksum());
|
public ChecksumCell getChecksum(File root) {
|
||||||
} else if (checksum.getState() == Checksum.State.ERROR) {
|
return hashes.get(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Collection<ChecksumCell> values() {
|
||||||
|
return Collections.unmodifiableCollection(hashes.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void add(ChecksumCell entry) {
|
||||||
|
hashes.put(entry.getRoot(), entry);
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void updateState() {
|
||||||
|
// update state
|
||||||
|
state = getState(hashes.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected State getState(Collection<ChecksumCell> entries) {
|
||||||
|
// check states before we bother comparing the hash values
|
||||||
|
for (ChecksumCell entry : entries) {
|
||||||
|
if (entry.getState() == ChecksumCell.State.ERROR) {
|
||||||
|
// one error cell -> error state
|
||||||
return State.ERROR;
|
return State.ERROR;
|
||||||
} else {
|
} else if (entry.getState() != ChecksumCell.State.READY) {
|
||||||
|
// one cell that is not ready yet -> unknown state
|
||||||
return State.UNKNOWN;
|
return State.UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checksums.size() > 1) {
|
// compare hash values
|
||||||
// checksums do not match
|
Set<String> checksumSet = new HashSet<String>(2);
|
||||||
|
Set<State> verdictSet = EnumSet.noneOf(State.class);
|
||||||
|
|
||||||
|
for (HashType type : HashType.values()) {
|
||||||
|
checksumSet.clear();
|
||||||
|
|
||||||
|
for (ChecksumCell entry : entries) {
|
||||||
|
String checksum = entry.getChecksum(type);
|
||||||
|
|
||||||
|
if (checksum != null) {
|
||||||
|
checksumSet.add(checksum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verdictSet.add(getVerdict(checksumSet));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ERROR > WARNING > OK > UNKOWN
|
||||||
|
return Collections.max(verdictSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected State getVerdict(Set<String> checksumSet) {
|
||||||
|
if (checksumSet.size() < 1) {
|
||||||
|
// no hash values
|
||||||
|
return State.UNKNOWN;
|
||||||
|
} else if (checksumSet.size() > 1) {
|
||||||
|
// hashes don't match, something is wrong
|
||||||
return State.ERROR;
|
return State.ERROR;
|
||||||
|
} else {
|
||||||
|
// all hashes match
|
||||||
|
if (embeddedChecksum != null) {
|
||||||
|
String checksum = checksumSet.iterator().next();
|
||||||
|
|
||||||
|
if (checksum.length() == embeddedChecksum.length() && !checksum.equalsIgnoreCase(embeddedChecksum)) {
|
||||||
|
return State.WARNING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return State.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checksums.isEmpty() && embeddedChecksum != null) {
|
|
||||||
// check if the embedded checksum matches
|
|
||||||
if (!checksums.contains(embeddedChecksum))
|
|
||||||
return State.WARNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return State.OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Checksum getChecksum(File column) {
|
@Override
|
||||||
return checksumMap.get(column);
|
public String toString() {
|
||||||
|
return String.format("%s %s", name, hashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Collection<Checksum> getChecksums() {
|
|
||||||
return checksumMap.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void putChecksum(File column, Checksum checksum) {
|
|
||||||
checksumMap.put(column, checksum);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void removeChecksum(File column) {
|
|
||||||
checksumMap.remove(column);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,24 +23,25 @@ class ChecksumTableCellRenderer extends DefaultTableCellRenderer {
|
||||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
super.getTableCellRendererComponent(table, null, isSelected, false, row, column);
|
super.getTableCellRendererComponent(table, null, isSelected, false, row, column);
|
||||||
|
|
||||||
if (value == null)
|
if (value instanceof ChecksumCell) {
|
||||||
return this;
|
ChecksumCell checksum = (ChecksumCell) value;
|
||||||
|
|
||||||
Checksum checksum = (Checksum) value;
|
switch (checksum.getState()) {
|
||||||
|
case READY:
|
||||||
switch (checksum.getState()) {
|
setText(checksum.getChecksum(HashType.CRC32));
|
||||||
case READY:
|
break;
|
||||||
setText(checksum.getChecksumString());
|
case PENDING:
|
||||||
return this;
|
setText("Pending ...");
|
||||||
case PENDING:
|
break;
|
||||||
setText("Pending ...");
|
case ERROR:
|
||||||
return this;
|
setText(checksum.getError().getMessage());
|
||||||
case ERROR:
|
break;
|
||||||
setText(checksum.getErrorMessage());
|
default:
|
||||||
return this;
|
return progressBarRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||||
default:
|
}
|
||||||
return progressBarRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,9 +62,11 @@ class ChecksumTableCellRenderer extends DefaultTableCellRenderer {
|
||||||
|
|
||||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
|
||||||
Checksum checksum = (Checksum) value;
|
ChecksumComputationTask task = ((ChecksumCell) value).getTask();
|
||||||
|
|
||||||
progressBar.setValue(checksum.getProgress());
|
if (task != null) {
|
||||||
|
progressBar.setValue(task.getProgress());
|
||||||
|
}
|
||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
this.setBackground(table.getSelectionBackground());
|
this.setBackground(table.getSelectionBackground());
|
||||||
|
|
|
@ -5,11 +5,9 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Formatter;
|
||||||
import java.util.Map.Entry;
|
import net.sourceforge.filebot.Settings;
|
||||||
|
|
||||||
import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
|
import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
|
||||||
|
@ -26,19 +24,19 @@ public class ChecksumTableExportHandler extends TextFileExportHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canExport() {
|
public boolean canExport() {
|
||||||
return model.getRowCount() > 0 && model.getChecksumColumnCount() > 0;
|
return model.getRowCount() > 0 && model.getChecksumList().size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void export(PrintWriter out) {
|
public void export(Formatter out) {
|
||||||
export(out, model.getChecksumColumn(0));
|
export(out, model.getChecksumList().get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDefaultFileName() {
|
public String getDefaultFileName() {
|
||||||
return getDefaultFileName(model.getChecksumColumn(0));
|
return getDefaultFileName(model.getChecksumList().get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,27 +44,21 @@ public class ChecksumTableExportHandler extends TextFileExportHandler {
|
||||||
PrintWriter out = new PrintWriter(file, "UTF-8");
|
PrintWriter out = new PrintWriter(file, "UTF-8");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
export(out, column);
|
export(new Formatter(out), column);
|
||||||
} finally {
|
} finally {
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void export(PrintWriter out, File column) {
|
public void export(Formatter out, File column) {
|
||||||
|
out.format("; Generated by %s on %tF at %<tT%n", Settings.getApplicationName(), new Date());
|
||||||
|
out.format(";%n");
|
||||||
|
out.format(";%n");
|
||||||
|
|
||||||
SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd");
|
for (ChecksumRow row : model) {
|
||||||
SimpleDateFormat time = new SimpleDateFormat("HH:mm:ss");
|
//TODO select hash type
|
||||||
|
out.format("%s %s%n", row.getName(), row.getChecksum(column).getChecksum(HashType.CRC32));
|
||||||
Date now = new Date();
|
|
||||||
out.println("; Generated by FileBot on " + date.format(now) + " at " + time.format(now));
|
|
||||||
out.println(";");
|
|
||||||
out.println(";");
|
|
||||||
|
|
||||||
Map<String, Checksum> checksumMap = model.getChecksumColumn(column);
|
|
||||||
|
|
||||||
for (Entry<String, Checksum> entry : checksumMap.entrySet()) {
|
|
||||||
out.println(String.format("%s %s", entry.getKey(), entry.getValue()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,20 @@
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import static javax.swing.event.TableModelEvent.UPDATE;
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.AbstractList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.swing.event.TableModelEvent;
|
import javax.swing.event.TableModelEvent;
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
@ -19,92 +24,86 @@ import javax.swing.table.TableModel;
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
|
||||||
|
|
||||||
class ChecksumTableModel extends AbstractTableModel {
|
class ChecksumTableModel extends AbstractTableModel implements Iterable<ChecksumRow> {
|
||||||
|
|
||||||
private List<ChecksumRow> rows = new ArrayList<ChecksumRow>(50);
|
private final IndexedMap<String, ChecksumRow> rows = new IndexedMap<String, ChecksumRow>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String key(ChecksumRow value) {
|
||||||
|
return value.getName();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
private final List<File> columns = new ArrayList<File>();
|
||||||
* Hash map for fast access to the row of a given name
|
|
||||||
*/
|
|
||||||
private Map<String, ChecksumRow> rowMap = new HashMap<String, ChecksumRow>(50);
|
|
||||||
|
|
||||||
private List<File> columns = new ArrayList<File>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checksum start at column 3
|
|
||||||
*/
|
|
||||||
private static final int checksumColumnOffset = 2;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName(int columnIndex) {
|
public String getColumnName(int columnIndex) {
|
||||||
if (columnIndex == 0)
|
switch (columnIndex) {
|
||||||
return "State";
|
case 0:
|
||||||
|
return "State";
|
||||||
if (columnIndex == 1)
|
case 1:
|
||||||
return "Name";
|
return "Name";
|
||||||
|
default:
|
||||||
if (columnIndex >= checksumColumnOffset) {
|
// works for files too and simply returns the name unchanged
|
||||||
File column = columns.get(columnIndex - checksumColumnOffset);
|
return FileUtilities.getFolderName(getColumnRoot(columnIndex));
|
||||||
|
|
||||||
// works for files too and simply returns the name unchanged
|
|
||||||
return FileUtilities.getFolderName(column);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<?> getColumnClass(int columnIndex) {
|
public Class<?> getColumnClass(int columnIndex) {
|
||||||
if (columnIndex == 0)
|
switch (columnIndex) {
|
||||||
return ChecksumRow.State.class;
|
case 0:
|
||||||
|
return ChecksumRow.State.class;
|
||||||
if (columnIndex == 1)
|
case 1:
|
||||||
return String.class;
|
return String.class;
|
||||||
|
default:
|
||||||
if (columnIndex >= checksumColumnOffset)
|
return ChecksumCell.class;
|
||||||
return Checksum.class;
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public File getColumnRoot(int columnIndex) {
|
||||||
|
return columns.get(columnIndex - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getColumnCount() {
|
public int getColumnCount() {
|
||||||
return checksumColumnOffset + getChecksumColumnCount();
|
return columns.size() + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getChecksumColumnCount() {
|
public List<File> getChecksumList() {
|
||||||
return columns.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<File> getChecksumColumns() {
|
|
||||||
return Collections.unmodifiableList(columns);
|
return Collections.unmodifiableList(columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return rows.size();
|
return rows.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
ChecksumRow row = rows.get(rowIndex);
|
ChecksumRow row = rows.get(rowIndex);
|
||||||
|
|
||||||
if (columnIndex == 0)
|
switch (columnIndex) {
|
||||||
return row.getState();
|
case 0:
|
||||||
|
return row.getState();
|
||||||
if (columnIndex == 1)
|
case 1:
|
||||||
return row.getName();
|
return row.getName();
|
||||||
|
default:
|
||||||
if (columnIndex >= checksumColumnOffset) {
|
return row.getChecksum(getColumnRoot(columnIndex));
|
||||||
File column = columns.get(columnIndex - checksumColumnOffset);
|
|
||||||
return row.getChecksum(column);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<ChecksumRow> iterator() {
|
||||||
|
return rows.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,7 +111,22 @@ class ChecksumTableModel extends AbstractTableModel {
|
||||||
int firstRow = getRowCount();
|
int firstRow = getRowCount();
|
||||||
|
|
||||||
for (ChecksumCell entry : list) {
|
for (ChecksumCell entry : list) {
|
||||||
addChecksum(entry.getName(), entry.getChecksum(), entry.getColumn());
|
ChecksumRow row = rows.getByKey(entry.getName());
|
||||||
|
|
||||||
|
if (row == null) {
|
||||||
|
row = new ChecksumRow(entry.getName());
|
||||||
|
rows.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
row.add(entry);
|
||||||
|
|
||||||
|
// listen to changes (progress, state)
|
||||||
|
entry.addPropertyChangeListener(progressListener);
|
||||||
|
|
||||||
|
if (!columns.contains(entry.getRoot())) {
|
||||||
|
columns.add(entry.getRoot());
|
||||||
|
fireTableStructureChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int lastRow = getRowCount() - 1;
|
int lastRow = getRowCount() - 1;
|
||||||
|
@ -123,125 +137,142 @@ class ChecksumTableModel extends AbstractTableModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addChecksum(String name, Checksum checksum, File column) {
|
public void remove(int... index) {
|
||||||
ChecksumRow row = rowMap.get(name);
|
for (int i : index) {
|
||||||
|
for (ChecksumCell entry : rows.get(i).values()) {
|
||||||
if (row == null) {
|
entry.removePropertyChangeListener(progressListener);
|
||||||
row = new ChecksumRow(name);
|
entry.dispose();
|
||||||
rows.add(row);
|
|
||||||
rowMap.put(name, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
row.putChecksum(column, checksum);
|
|
||||||
checksum.addPropertyChangeListener(checksumListener);
|
|
||||||
|
|
||||||
if (!columns.contains(column)) {
|
|
||||||
columns.add(column);
|
|
||||||
fireTableStructureChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void removeRows(int... rowIndices) {
|
|
||||||
ArrayList<ChecksumRow> rowsToRemove = new ArrayList<ChecksumRow>(rowIndices.length);
|
|
||||||
|
|
||||||
for (int i : rowIndices) {
|
|
||||||
ChecksumRow row = rows.get(i);
|
|
||||||
rowsToRemove.add(rows.get(i));
|
|
||||||
|
|
||||||
for (Checksum checksum : row.getChecksums()) {
|
|
||||||
checksum.cancelComputationTask();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rowMap.remove(row.getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.removeAll(rowsToRemove);
|
// remove rows
|
||||||
fireTableRowsDeleted(rowIndices[0], rowIndices[rowIndices.length - 1]);
|
rows.removeAll(index);
|
||||||
|
|
||||||
|
fireTableRowsDeleted(index[0], index[index.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
columns.clear();
|
columns.clear();
|
||||||
rows.clear();
|
rows.clear();
|
||||||
rowMap.clear();
|
|
||||||
fireTableStructureChanged();
|
fireTableStructureChanged();
|
||||||
|
|
||||||
fireTableDataChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final PropertyChangeListener progressListener = new PropertyChangeListener() {
|
||||||
public File getChecksumColumn(int columnIndex) {
|
|
||||||
return columns.get(columnIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Map<String, Checksum> getChecksumColumn(File column) {
|
|
||||||
LinkedHashMap<String, Checksum> checksumMap = new LinkedHashMap<String, Checksum>();
|
|
||||||
|
|
||||||
for (ChecksumRow row : rows) {
|
private final MutableTableModelEvent mutableUpdateEvent = new MutableTableModelEvent(ChecksumTableModel.this, UPDATE);
|
||||||
Checksum checksum = row.getChecksum(column);
|
|
||||||
|
|
||||||
if ((checksum != null) && (checksum.getState() == Checksum.State.READY)) {
|
|
||||||
checksumMap.put(row.getName(), checksum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return checksumMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final PropertyChangeListener checksumListener = new PropertyChangeListener() {
|
|
||||||
|
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
fireTableChanged(new ChecksumTableModelEvent(ChecksumTableModel.this));
|
ChecksumCell entry = (ChecksumCell) evt.getSource();
|
||||||
|
|
||||||
|
int index = rows.getIndexByKey(entry.getName());
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
rows.get(index).updateState();
|
||||||
|
fireTableChanged(mutableUpdateEvent.setRow(index));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
public static class ChecksumCell {
|
protected static class MutableTableModelEvent extends TableModelEvent {
|
||||||
|
|
||||||
private final String name;
|
public MutableTableModelEvent(TableModel source, int type) {
|
||||||
private final Checksum checksum;
|
super(source, 0, 0, ALL_COLUMNS, type);
|
||||||
private final File column;
|
|
||||||
|
|
||||||
|
|
||||||
public ChecksumCell(String name, Checksum checksum, File column) {
|
|
||||||
this.name = name;
|
|
||||||
this.checksum = checksum;
|
|
||||||
this.column = column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getName() {
|
public MutableTableModelEvent setRow(int row) {
|
||||||
return name;
|
this.firstRow = row;
|
||||||
}
|
this.lastRow = row;
|
||||||
|
|
||||||
|
return this;
|
||||||
public Checksum getChecksum() {
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public File getColumn() {
|
|
||||||
return column;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getName();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class ChecksumTableModelEvent extends TableModelEvent {
|
protected static abstract class IndexedMap<K, V> extends AbstractList<V> implements Set<V> {
|
||||||
|
|
||||||
public static final int CHECKSUM_PROGRESS = 10;
|
private final Map<K, Integer> indexMap = new HashMap<K, Integer>(64);
|
||||||
|
private final List<V> list = new ArrayList<V>(64);
|
||||||
|
|
||||||
|
|
||||||
public ChecksumTableModelEvent(TableModel source) {
|
public abstract K key(V value);
|
||||||
super(source);
|
|
||||||
type = CHECKSUM_PROGRESS;
|
|
||||||
|
@Override
|
||||||
|
public V get(int index) {
|
||||||
|
return list.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (index == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(V value) {
|
||||||
|
K key = key(value);
|
||||||
|
Integer index = indexMap.get(key);
|
||||||
|
|
||||||
|
if (index == null && list.add(value)) {
|
||||||
|
indexMap.put(key, lastIndexOf(value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void removeAll(int... index) {
|
||||||
|
// sort index array
|
||||||
|
Arrays.sort(index);
|
||||||
|
|
||||||
|
// remove in reverse
|
||||||
|
for (int i = index.length - 1; i >= 0; i--) {
|
||||||
|
V value = list.remove(index[i]);
|
||||||
|
indexMap.remove(key(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateIndexMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void updateIndexMap() {
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
indexMap.put(key(list.get(i)), i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
list.clear();
|
||||||
|
indexMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
interface Hash {
|
||||||
|
|
||||||
|
public void update(byte[] bytes, int off, int len);
|
||||||
|
|
||||||
|
|
||||||
|
public String digest();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
|
||||||
|
enum HashType {
|
||||||
|
|
||||||
|
CRC32 {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Hash newInstance() {
|
||||||
|
return new ChecksumHash(new CRC32());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MD5 {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Hash newInstance() {
|
||||||
|
return new MessageDigestHash("MD5");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SHA1 {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Hash newInstance() {
|
||||||
|
return new MessageDigestHash("SHA-1");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract Hash newInstance();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
|
||||||
|
class MessageDigestHash implements Hash {
|
||||||
|
|
||||||
|
private final MessageDigest md;
|
||||||
|
|
||||||
|
|
||||||
|
public MessageDigestHash(String algorithm) {
|
||||||
|
try {
|
||||||
|
this.md = MessageDigest.getInstance(algorithm);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public MessageDigestHash(MessageDigest md) {
|
||||||
|
this.md = md;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(byte[] bytes, int off, int len) {
|
||||||
|
md.update(bytes, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String digest() {
|
||||||
|
// e.g. %032x (format for MD-5)
|
||||||
|
return String.format("%0" + (md.getDigestLength() * 2) + "x", new BigInteger(1, md.digest()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,24 +5,19 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.border.TitledBorder;
|
import javax.swing.border.TitledBorder;
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
import net.sourceforge.filebot.ui.FileBotPanel;
|
import net.sourceforge.filebot.ui.FileBotPanel;
|
||||||
import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
|
import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
|
||||||
import net.sourceforge.filebot.ui.SelectDialog;
|
|
||||||
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
||||||
import net.sourceforge.filebot.ui.transfer.SaveAction;
|
import net.sourceforge.filebot.ui.transfer.SaveAction;
|
||||||
import net.sourceforge.tuned.FileUtilities;
|
|
||||||
import net.sourceforge.tuned.MessageHandler;
|
import net.sourceforge.tuned.MessageHandler;
|
||||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||||
|
|
||||||
|
@ -82,7 +77,8 @@ public class SfvPanel extends FileBotPanel {
|
||||||
|
|
||||||
int row = sfvTable.getSelectionModel().getMinSelectionIndex();
|
int row = sfvTable.getSelectionModel().getMinSelectionIndex();
|
||||||
|
|
||||||
sfvTable.removeRows(sfvTable.getSelectedRows());
|
// remove selected rows
|
||||||
|
sfvTable.getModel().remove(sfvTable.getSelectedRows());
|
||||||
|
|
||||||
int maxRow = sfvTable.getRowCount() - 1;
|
int maxRow = sfvTable.getRowCount() - 1;
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ import javax.swing.table.TableColumn;
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileBotUtilities;
|
import net.sourceforge.filebot.FileBotUtilities;
|
||||||
import net.sourceforge.filebot.ui.panel.sfv.ChecksumTableModel.ChecksumTableModelEvent;
|
|
||||||
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +26,6 @@ class SfvTable extends JTable {
|
||||||
|
|
||||||
|
|
||||||
public SfvTable() {
|
public SfvTable() {
|
||||||
|
|
||||||
transferablePolicy = new SfvTransferablePolicy(getModel(), checksumComputationService);
|
transferablePolicy = new SfvTransferablePolicy(getModel(), checksumComputationService);
|
||||||
exportHandler = new ChecksumTableExportHandler(getModel());
|
exportHandler = new ChecksumTableExportHandler(getModel());
|
||||||
|
|
||||||
|
@ -48,7 +46,7 @@ class SfvTable extends JTable {
|
||||||
// highlight CRC32 patterns in filenames in green and with smaller font-size
|
// highlight CRC32 patterns in filenames in green and with smaller font-size
|
||||||
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(FileBotUtilities.EMBEDDED_CHECKSUM_PATTERN, "#009900", "smaller"));
|
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(FileBotUtilities.EMBEDDED_CHECKSUM_PATTERN, "#009900", "smaller"));
|
||||||
setDefaultRenderer(ChecksumRow.State.class, new StateIconTableCellRenderer());
|
setDefaultRenderer(ChecksumRow.State.class, new StateIconTableCellRenderer());
|
||||||
setDefaultRenderer(Checksum.class, new ChecksumTableCellRenderer());
|
setDefaultRenderer(ChecksumCell.class, new ChecksumTableCellRenderer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,6 +89,7 @@ class SfvTable extends JTable {
|
||||||
|
|
||||||
for (int i = 0; i < getColumnCount(); i++) {
|
for (int i = 0; i < getColumnCount(); i++) {
|
||||||
TableColumn column = getColumnModel().getColumn(i);
|
TableColumn column = getColumnModel().getColumn(i);
|
||||||
|
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
column.setPreferredWidth(45);
|
column.setPreferredWidth(45);
|
||||||
} else if (i == 1) {
|
} else if (i == 1) {
|
||||||
|
@ -110,26 +109,15 @@ class SfvTable extends JTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void removeRows(int... rowIndices) {
|
|
||||||
getModel().removeRows(rowIndices);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tableChanged(TableModelEvent e) {
|
public void tableChanged(TableModelEvent e) {
|
||||||
// only request repaint when progress changes. Selection will go haywire if you don't.
|
//TODO CCS in SfvPanel??
|
||||||
if (e.getType() == ChecksumTableModelEvent.CHECKSUM_PROGRESS) {
|
|
||||||
repaint();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.getType() == TableModelEvent.DELETE) {
|
if (e.getType() == TableModelEvent.DELETE) {
|
||||||
// remove cancelled tasks from queue
|
// remove cancelled tasks from queue
|
||||||
checksumComputationService.purge();
|
checksumComputationService.purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
super.tableChanged(e);
|
super.tableChanged(e);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,14 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
import static net.sourceforge.filebot.FileBotUtilities.SFV_FILES;
|
import static net.sourceforge.filebot.FileBotUtilities.SFV_FILES;
|
||||||
import static net.sourceforge.tuned.FileUtilities.containsOnly;
|
import static net.sourceforge.tuned.FileUtilities.containsOnly;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
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.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
@ -19,14 +21,14 @@ import java.util.regex.Pattern;
|
||||||
import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy;
|
import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy;
|
||||||
|
|
||||||
|
|
||||||
class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTableModel.ChecksumCell> {
|
class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumCell> {
|
||||||
|
|
||||||
private final ChecksumTableModel tableModel;
|
private final ChecksumTableModel model;
|
||||||
private final ChecksumComputationService checksumComputationService;
|
private final ChecksumComputationService checksumComputationService;
|
||||||
|
|
||||||
|
|
||||||
public SfvTransferablePolicy(ChecksumTableModel tableModel, ChecksumComputationService checksumComputationService) {
|
public SfvTransferablePolicy(ChecksumTableModel model, ChecksumComputationService checksumComputationService) {
|
||||||
this.tableModel = tableModel;
|
this.model = model;
|
||||||
this.checksumComputationService = checksumComputationService;
|
this.checksumComputationService = checksumComputationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,46 +42,58 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
|
||||||
@Override
|
@Override
|
||||||
protected void clear() {
|
protected void clear() {
|
||||||
checksumComputationService.reset();
|
checksumComputationService.reset();
|
||||||
tableModel.clear();
|
model.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void process(List<ChecksumTableModel.ChecksumCell> chunks) {
|
protected void process(List<ChecksumCell> chunks) {
|
||||||
tableModel.addAll(chunks);
|
model.addAll(chunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void loadSfvFile(File sfvFile) {
|
protected void loadSfvFile(File sfvFile, Executor executor) {
|
||||||
try {
|
try {
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(sfvFile), "UTF-8"));
|
// 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");
|
||||||
|
|
||||||
String line = null;
|
try {
|
||||||
Pattern pattern = Pattern.compile("(.*)\\s+(\\p{XDigit}{8})");
|
Pattern pattern = Pattern.compile("(.+)\\s+(\\p{XDigit}{8})");
|
||||||
|
|
||||||
while (((line = in.readLine()) != null) && !Thread.interrupted()) {
|
|
||||||
if (line.startsWith(";"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Matcher matcher = pattern.matcher(line);
|
while (scanner.hasNextLine()) {
|
||||||
|
String line = scanner.nextLine();
|
||||||
if (!matcher.matches())
|
|
||||||
continue;
|
if (line.startsWith(";"))
|
||||||
|
continue;
|
||||||
String filename = matcher.group(1);
|
|
||||||
String checksumString = matcher.group(2);
|
Matcher matcher = pattern.matcher(line);
|
||||||
|
|
||||||
publish(new ChecksumTableModel.ChecksumCell(filename, new Checksum(checksumString), sfvFile));
|
if (!matcher.matches())
|
||||||
|
continue;
|
||||||
File column = sfvFile.getParentFile();
|
|
||||||
File file = new File(column, filename);
|
String filename = matcher.group(1);
|
||||||
|
String checksum = matcher.group(2);
|
||||||
if (file.exists()) {
|
|
||||||
publish(new ChecksumTableModel.ChecksumCell(filename, checksumComputationService.schedule(file, column), column));
|
publish(new ChecksumCell(filename, sfvFile, Collections.singletonMap(HashType.CRC32, checksum)));
|
||||||
|
|
||||||
|
File column = sfvFile.getParentFile();
|
||||||
|
File file = new File(column, filename);
|
||||||
|
|
||||||
|
if (file.exists()) {
|
||||||
|
ChecksumComputationTask task = new ChecksumComputationTask(file);
|
||||||
|
|
||||||
|
publish(new ChecksumCell(filename, column, task));
|
||||||
|
|
||||||
|
executor.execute(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
scanner.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
in.close();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// should not happen
|
// should not happen
|
||||||
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
|
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
|
||||||
|
@ -95,44 +109,52 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void load(List<File> files) {
|
protected void load(List<File> files) {
|
||||||
|
ExecutorService executor = checksumComputationService.newExecutor();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (containsOnly(files, SFV_FILES)) {
|
if (containsOnly(files, SFV_FILES)) {
|
||||||
// one or more sfv files
|
// one or more sfv files
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
loadSfvFile(file);
|
loadSfvFile(file, executor);
|
||||||
}
|
}
|
||||||
} else if ((files.size() == 1) && files.get(0).isDirectory()) {
|
} else if ((files.size() == 1) && files.get(0).isDirectory()) {
|
||||||
// one single folder
|
// one single folder
|
||||||
File file = files.get(0);
|
File file = files.get(0);
|
||||||
|
|
||||||
for (File f : file.listFiles()) {
|
for (File f : file.listFiles()) {
|
||||||
load(f, file, "");
|
load(f, file, "", executor);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// bunch of files
|
// bunch of files
|
||||||
for (File f : files) {
|
for (File f : files) {
|
||||||
load(f, f.getParentFile(), "");
|
load(f, f.getParentFile(), "", executor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// supposed to happen if background execution was aborted
|
// supposed to happen if background execution was aborted
|
||||||
|
} finally {
|
||||||
|
executor.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void load(File file, File column, String prefix) throws InterruptedException {
|
protected void load(File file, File root, String prefix, Executor executor) throws InterruptedException {
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
|
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
// load all files in the file tree
|
// load all files in the file tree
|
||||||
String newPrefix = prefix + file.getName() + "/";
|
String newPrefix = prefix + file.getName() + "/";
|
||||||
|
|
||||||
for (File f : file.listFiles()) {
|
for (File f : file.listFiles()) {
|
||||||
load(f, column, newPrefix);
|
load(f, root, newPrefix, executor);
|
||||||
}
|
}
|
||||||
} else if (file.isFile()) {
|
} else if (file.isFile()) {
|
||||||
publish(new ChecksumTableModel.ChecksumCell(prefix + file.getName(), checksumComputationService.schedule(file, column), column));
|
ChecksumComputationTask task = new ChecksumComputationTask(file);
|
||||||
|
|
||||||
|
publish(new ChecksumCell(prefix + file.getName(), root, task));
|
||||||
|
|
||||||
|
executor.execute(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JTable;
|
import javax.swing.JTable;
|
||||||
|
@ -10,17 +12,20 @@ import javax.swing.SwingConstants;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
|
import net.sourceforge.filebot.ui.panel.sfv.ChecksumRow.State;
|
||||||
|
|
||||||
|
|
||||||
class StateIconTableCellRenderer extends DefaultTableCellRenderer {
|
class StateIconTableCellRenderer extends DefaultTableCellRenderer {
|
||||||
|
|
||||||
private Icon warning = ResourceManager.getIcon("status.warning");
|
private final Map<State, Icon> icons = new EnumMap<State, Icon>(State.class);
|
||||||
private Icon error = ResourceManager.getIcon("status.error");
|
|
||||||
private Icon unknown = ResourceManager.getIcon("status.unknown");
|
|
||||||
private Icon ok = ResourceManager.getIcon("status.ok");
|
|
||||||
|
|
||||||
|
|
||||||
public StateIconTableCellRenderer() {
|
public StateIconTableCellRenderer() {
|
||||||
|
icons.put(State.UNKNOWN, ResourceManager.getIcon("status.unknown"));
|
||||||
|
icons.put(State.OK, ResourceManager.getIcon("status.ok"));
|
||||||
|
icons.put(State.WARNING, ResourceManager.getIcon("status.warning"));
|
||||||
|
icons.put(State.ERROR, ResourceManager.getIcon("status.error"));
|
||||||
|
|
||||||
setVerticalAlignment(SwingConstants.CENTER);
|
setVerticalAlignment(SwingConstants.CENTER);
|
||||||
setHorizontalAlignment(SwingConstants.CENTER);
|
setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
}
|
}
|
||||||
|
@ -30,22 +35,7 @@ class StateIconTableCellRenderer extends DefaultTableCellRenderer {
|
||||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
super.getTableCellRendererComponent(table, null, isSelected, false, row, column);
|
super.getTableCellRendererComponent(table, null, isSelected, false, row, column);
|
||||||
|
|
||||||
ChecksumRow.State state = (ChecksumRow.State) value;
|
setIcon(icons.get(value));
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case OK:
|
|
||||||
setIcon(ok);
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
setIcon(error);
|
|
||||||
break;
|
|
||||||
case WARNING:
|
|
||||||
setIcon(warning);
|
|
||||||
break;
|
|
||||||
case UNKNOWN:
|
|
||||||
setIcon(unknown);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
package net.sourceforge.filebot.ui.panel.sfv;
|
package net.sourceforge.filebot.ui.panel.sfv;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.filebot.ui.panel.sfv.ChecksumComputationService.TASK_COUNT_PROPERTY;
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
|
||||||
|
@ -9,6 +11,8 @@ import javax.swing.BorderFactory;
|
||||||
import javax.swing.Box;
|
import javax.swing.Box;
|
||||||
import javax.swing.BoxLayout;
|
import javax.swing.BoxLayout;
|
||||||
import javax.swing.JProgressBar;
|
import javax.swing.JProgressBar;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.Timer;
|
||||||
|
|
||||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||||
|
|
||||||
|
@ -19,13 +23,13 @@ class TotalProgressPanel extends Box {
|
||||||
|
|
||||||
private final JProgressBar progressBar = new JProgressBar(0, 0);
|
private final JProgressBar progressBar = new JProgressBar(0, 0);
|
||||||
|
|
||||||
private final ChecksumComputationService checksumComputationService;
|
private final ChecksumComputationService service;
|
||||||
|
|
||||||
|
|
||||||
public TotalProgressPanel(ChecksumComputationService checksumComputationService) {
|
public TotalProgressPanel(ChecksumComputationService checksumComputationService) {
|
||||||
super(BoxLayout.Y_AXIS);
|
super(BoxLayout.Y_AXIS);
|
||||||
|
|
||||||
this.checksumComputationService = checksumComputationService;
|
this.service = checksumComputationService;
|
||||||
|
|
||||||
// invisible by default
|
// invisible by default
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
@ -38,40 +42,49 @@ class TotalProgressPanel extends Box {
|
||||||
|
|
||||||
add(progressBar);
|
add(progressBar);
|
||||||
|
|
||||||
checksumComputationService.addPropertyChangeListener(progressListener);
|
checksumComputationService.addPropertyChangeListener(TASK_COUNT_PROPERTY, progressListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyChangeListener progressListener = new PropertyChangeListener() {
|
private final PropertyChangeListener progressListener = new PropertyChangeListener() {
|
||||||
|
|
||||||
|
private Timer setVisibleTimer;
|
||||||
|
|
||||||
|
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
final int completedTaskCount = service.getCompletedTaskCount();
|
||||||
|
final int totalTaskCount = service.getTotalTaskCount();
|
||||||
|
|
||||||
String property = evt.getPropertyName();
|
// invoke on EDT
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
if (property == ChecksumComputationService.ACTIVE_PROPERTY) {
|
|
||||||
Boolean active = (Boolean) evt.getNewValue();
|
|
||||||
|
|
||||||
if (active) {
|
@Override
|
||||||
TunedUtilities.invokeLater(millisToSetVisible, new Runnable() {
|
public void run() {
|
||||||
|
if (completedTaskCount < totalTaskCount) {
|
||||||
@Override
|
if (setVisibleTimer == null) {
|
||||||
public void run() {
|
setVisibleTimer = TunedUtilities.invokeLater(millisToSetVisible, new Runnable() {
|
||||||
setVisible(checksumComputationService.isActive());
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
setVisible(service.getTaskCount() > service.getCompletedTaskCount());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
} else {
|
if (setVisibleTimer != null) {
|
||||||
// hide when not active
|
setVisibleTimer.stop();
|
||||||
setVisible(false);
|
setVisibleTimer = null;
|
||||||
}
|
}
|
||||||
} else if (property == ChecksumComputationService.REMAINING_TASK_COUNT_PROPERTY) {
|
|
||||||
|
// hide when not active
|
||||||
int taskCount = checksumComputationService.getActiveSessionTaskCount();
|
setVisible(false);
|
||||||
int progress = taskCount - checksumComputationService.getRemainingTaskCount();
|
}
|
||||||
|
|
||||||
progressBar.setValue(progress);
|
progressBar.setValue(completedTaskCount);
|
||||||
progressBar.setMaximum(taskCount);
|
progressBar.setMaximum(totalTaskCount);
|
||||||
|
|
||||||
progressBar.setString(progressBar.getValue() + " / " + progressBar.getMaximum());
|
progressBar.setString(completedTaskCount + " / " + totalTaskCount);
|
||||||
}
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ import java.util.EventListener;
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
import javax.swing.JScrollPane;
|
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
||||||
|
import net.sourceforge.tuned.DownloadTask;
|
||||||
|
|
||||||
import ca.odell.glazedlists.BasicEventList;
|
import ca.odell.glazedlists.BasicEventList;
|
||||||
import ca.odell.glazedlists.EventList;
|
import ca.odell.glazedlists.EventList;
|
||||||
|
@ -24,7 +25,33 @@ public class SubtitlePackagePanel extends JComponent {
|
||||||
|
|
||||||
public SubtitlePackagePanel() {
|
public SubtitlePackagePanel() {
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
add(new JScrollPane(createList()), BorderLayout.CENTER);
|
add(createList(), BorderLayout.CENTER);
|
||||||
|
model.add(new SubtitlePackage(new SubtitleDescriptor() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DownloadTask createDownloadTask() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getArchiveType() {
|
||||||
|
return ArchiveType.ZIP.getExtension();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLanguageName() {
|
||||||
|
return "english";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Firefly 1x01 The Train Job.srt";
|
||||||
|
}
|
||||||
|
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,11 +60,15 @@ public class SubtitlePackagePanel extends JComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected JList createList() {
|
protected JComponent createList() {
|
||||||
ObservableElementList<SubtitlePackage> observableList = new ObservableElementList<SubtitlePackage>(model, new SubtitlePackageConnector());
|
ObservableElementList<SubtitlePackage> observableList = new ObservableElementList<SubtitlePackage>(model, new SubtitlePackageConnector());
|
||||||
|
|
||||||
JList list = new JList(new EventListModel<SubtitlePackage>(observableList));
|
JList list = new JList(new EventListModel<SubtitlePackage>(observableList));
|
||||||
|
|
||||||
|
list.setCellRenderer(new SubtitleCellRenderer());
|
||||||
|
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
|
||||||
|
list.setVisibleRowCount(-1);
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,9 +82,14 @@ public class SubtitlePackagePanel extends JComponent {
|
||||||
private ObservableElementList<SubtitlePackage> list = null;
|
private ObservableElementList<SubtitlePackage> list = null;
|
||||||
|
|
||||||
|
|
||||||
public EventListener installListener(SubtitlePackage element) {
|
public EventListener installListener(final SubtitlePackage element) {
|
||||||
PropertyChangeListener listener = new SubtitlePackageListener(element);
|
PropertyChangeListener listener = new PropertyChangeListener() {
|
||||||
element.getDownloadTask().addPropertyChangeListener(listener);
|
|
||||||
|
@Override
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
list.elementChanged(element);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
@ -64,24 +100,10 @@ public class SubtitlePackagePanel extends JComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setObservableElementList(ObservableElementList<SubtitlePackage> list) {
|
@SuppressWarnings("unchecked")
|
||||||
this.list = list;
|
@Override
|
||||||
}
|
public void setObservableElementList(ObservableElementList<? extends SubtitlePackage> list) {
|
||||||
|
this.list = (ObservableElementList<SubtitlePackage>) list;
|
||||||
|
|
||||||
protected class SubtitlePackageListener implements PropertyChangeListener {
|
|
||||||
|
|
||||||
private final SubtitlePackage subtitlePackage;
|
|
||||||
|
|
||||||
|
|
||||||
public SubtitlePackageListener(SubtitlePackage subtitlePackage) {
|
|
||||||
this.subtitlePackage = subtitlePackage;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
|
||||||
list.elementChanged(subtitlePackage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import java.beans.PropertyChangeListener;
|
||||||
import java.beans.PropertyChangeSupport;
|
import java.beans.PropertyChangeSupport;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
|
@ -76,7 +78,7 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class BackgroundWorker extends SwingWorker<Object, V> {
|
private class BackgroundWorker extends SwingWorker<Void, V> {
|
||||||
|
|
||||||
private final List<File> files;
|
private final List<File> files;
|
||||||
|
|
||||||
|
@ -87,8 +89,13 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object doInBackground() {
|
protected Void doInBackground() {
|
||||||
load(files);
|
try {
|
||||||
|
load(files);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger("global").log(Level.WARNING, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy {
|
||||||
/**
|
/**
|
||||||
* Pattern that will match Windows (\r\n), Unix (\n) and Mac (\r) line separators.
|
* Pattern that will match Windows (\r\n), Unix (\n) and Mac (\r) line separators.
|
||||||
*/
|
*/
|
||||||
public static final Pattern LINE_SEPARATOR = Pattern.compile("\r?\n|\r\n?");
|
public static final Pattern LINE_SEPARATOR = Pattern.compile("\r\n|[\r\n]");
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,7 +6,7 @@ import java.awt.datatransfer.Transferable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.util.Formatter;
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.TransferHandler;
|
import javax.swing.TransferHandler;
|
||||||
|
@ -17,7 +17,7 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
|
||||||
public abstract boolean canExport();
|
public abstract boolean canExport();
|
||||||
|
|
||||||
|
|
||||||
public abstract void export(PrintWriter out);
|
public abstract void export(Formatter out);
|
||||||
|
|
||||||
|
|
||||||
public abstract String getDefaultFileName();
|
public abstract String getDefaultFileName();
|
||||||
|
@ -28,7 +28,7 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
|
||||||
PrintWriter out = new PrintWriter(file, "UTF-8");
|
PrintWriter out = new PrintWriter(file, "UTF-8");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
export(out);
|
export(new Formatter(out));
|
||||||
} finally {
|
} finally {
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,8 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
|
||||||
@Override
|
@Override
|
||||||
public Transferable createTransferable(JComponent c) {
|
public Transferable createTransferable(JComponent c) {
|
||||||
// get transfer data
|
// get transfer data
|
||||||
StringWriter buffer = new StringWriter();
|
StringBuilder buffer = new StringBuilder();
|
||||||
export(new PrintWriter(buffer));
|
export(new Formatter(buffer));
|
||||||
|
|
||||||
return new LazyTextFileTransferable(buffer.toString(), getDefaultFileName());
|
return new LazyTextFileTransferable(buffer.toString(), getDefaultFileName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -135,7 +134,7 @@ public class AnidbClient implements EpisodeListClient {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception {
|
public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,23 +3,23 @@ package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collection;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
|
||||||
public interface EpisodeListClient {
|
public interface EpisodeListClient {
|
||||||
|
|
||||||
public Collection<SearchResult> search(String query) throws Exception;
|
public List<SearchResult> search(String query) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public boolean hasSingleSeasonSupport();
|
public boolean hasSingleSeasonSupport();
|
||||||
|
|
||||||
|
|
||||||
public Collection<Episode> getEpisodeList(SearchResult searchResult) throws Exception;
|
public List<Episode> getEpisodeList(SearchResult searchResult) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public Collection<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception;
|
public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public URI getEpisodeListLink(SearchResult searchResult);
|
public URI getEpisodeListLink(SearchResult searchResult);
|
||||||
|
|
|
@ -3,7 +3,7 @@ package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collection;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
@ -11,10 +11,10 @@ import javax.swing.Icon;
|
||||||
|
|
||||||
public interface SubtitleClient {
|
public interface SubtitleClient {
|
||||||
|
|
||||||
public Collection<SearchResult> search(String query) throws Exception;
|
public List<SearchResult> search(String query) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public Collection<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, Locale language) throws Exception;
|
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, Locale language) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
public URI getSubtitleListLink(SearchResult searchResult, Locale language);
|
public URI getSubtitleListLink(SearchResult searchResult, Locale language);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
package net.sourceforge.tuned;
|
package net.sourceforge.tuned;
|
||||||
|
|
||||||
|
|
||||||
public final class ExceptionUtil {
|
public final class ExceptionUtilities {
|
||||||
|
|
||||||
public static Throwable getRootCause(Throwable t) {
|
public static Throwable getRootCause(Throwable t) {
|
||||||
while (t.getCause() != null) {
|
while (t.getCause() != null) {
|
||||||
|
@ -25,7 +25,7 @@ public final class ExceptionUtil {
|
||||||
/**
|
/**
|
||||||
* Dummy constructor to prevent instantiation.
|
* Dummy constructor to prevent instantiation.
|
||||||
*/
|
*/
|
||||||
private ExceptionUtil() {
|
private ExceptionUtilities() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,7 +282,7 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
return constructor.newInstance(stringValue);
|
return constructor.newInstance(stringValue);
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
// try to throw the cause directly, e.g. NumberFormatException
|
// try to throw the cause directly, e.g. NumberFormatException
|
||||||
throw ExceptionUtil.asRuntimeException(e.getCause());
|
throw ExceptionUtilities.asRuntimeException(e.getCause());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import java.util.Arrays;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import net.sourceforge.tuned.ExceptionUtil;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +73,7 @@ public class SimpleLabelProvider<T> implements LabelProvider<T> {
|
||||||
try {
|
try {
|
||||||
return (String) getTextMethod.invoke(value);
|
return (String) getTextMethod.invoke(value);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw ExceptionUtil.asRuntimeException(e);
|
throw ExceptionUtilities.asRuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ public class SimpleLabelProvider<T> implements LabelProvider<T> {
|
||||||
try {
|
try {
|
||||||
return (Icon) getIconMethod.invoke(value);
|
return (Icon) getIconMethod.invoke(value);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw ExceptionUtil.asRuntimeException(e);
|
throw ExceptionUtilities.asRuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import javax.swing.KeyStroke;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.Timer;
|
import javax.swing.Timer;
|
||||||
|
|
||||||
import net.sourceforge.tuned.ExceptionUtil;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
|
||||||
|
|
||||||
public final class TunedUtilities {
|
public final class TunedUtilities {
|
||||||
|
@ -120,7 +120,7 @@ public final class TunedUtilities {
|
||||||
|
|
||||||
return listener;
|
return listener;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw ExceptionUtil.asRuntimeException(e);
|
throw ExceptionUtilities.asRuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ public final class TunedUtilities {
|
||||||
try {
|
try {
|
||||||
firePropertyChange.invoke(target, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
|
firePropertyChange.invoke(target, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw ExceptionUtil.asRuntimeException(e);
|
throw ExceptionUtilities.asRuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue