* 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.Preferences;
|
||||
|
||||
import net.sourceforge.tuned.ExceptionUtil;
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
import net.sourceforge.tuned.PreferencesList;
|
||||
import net.sourceforge.tuned.PreferencesMap;
|
||||
import net.sourceforge.tuned.PreferencesMap.Adapter;
|
||||
|
@ -96,7 +96,7 @@ public final class Settings {
|
|||
// remove entries
|
||||
prefs.clear();
|
||||
} 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.sourceforge.filebot.ResourceManager;
|
||||
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.SelectButtonTextField;
|
||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||
|
@ -184,7 +184,7 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
} catch (Exception e) {
|
||||
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) {
|
||||
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 {
|
||||
tab.setLoading(false);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
package net.sourceforge.filebot.ui;
|
||||
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Formatter;
|
||||
|
||||
import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
|
||||
|
||||
|
@ -24,9 +24,9 @@ public class FileBotListExportHandler extends TextFileExportHandler {
|
|||
|
||||
|
||||
@Override
|
||||
public void export(PrintWriter out) {
|
||||
public void export(Formatter out) {
|
||||
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.FolderNode;
|
||||
import net.sourceforge.tuned.ExceptionUtil;
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||
|
||||
|
@ -90,7 +90,7 @@ abstract class Tool<M> extends JComponent {
|
|||
try {
|
||||
setModel(get());
|
||||
} catch (Exception e) {
|
||||
if (ExceptionUtil.getRootCause(e) instanceof ConcurrentModificationException) {
|
||||
if (ExceptionUtilities.getRootCause(e) instanceof ConcurrentModificationException) {
|
||||
// if it happens, it is supposed to
|
||||
} else {
|
||||
// should not happen
|
||||
|
|
|
@ -49,14 +49,19 @@ class AutoFetchEpisodeListMatcher extends SwingWorker<List<Match<File, Episode>>
|
|||
}
|
||||
|
||||
|
||||
public Collection<File> remainingFiles() {
|
||||
return Collections.unmodifiableCollection(files);
|
||||
public List<File> remainingFiles() {
|
||||
return Collections.unmodifiableList(files);
|
||||
}
|
||||
|
||||
|
||||
protected Collection<String> detectSeriesNames(Collection<File> 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
|
||||
List<Episode> episodes = new ArrayList<Episode>();
|
||||
ExecutorService executor = Executors.newFixedThreadPool(tasks.size());
|
||||
|
|
|
@ -8,13 +8,12 @@ import java.util.Collection;
|
|||
import net.sourceforge.filebot.similarity.Match;
|
||||
import ca.odell.glazedlists.BasicEventList;
|
||||
import ca.odell.glazedlists.EventList;
|
||||
import ca.odell.glazedlists.GlazedLists;
|
||||
|
||||
|
||||
class RenameModel {
|
||||
|
||||
private final EventList<Object> names = GlazedLists.threadSafeList(new BasicEventList<Object>());
|
||||
private final EventList<FileEntry> files = GlazedLists.threadSafeList(new BasicEventList<FileEntry>());
|
||||
private final EventList<Object> names = new BasicEventList<Object>();
|
||||
private final EventList<FileEntry> files = new BasicEventList<FileEntry>();
|
||||
|
||||
|
||||
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.TVRageClient;
|
||||
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.LoadingOverlayPane;
|
||||
import ca.odell.glazedlists.event.ListEvent;
|
||||
|
@ -215,7 +215,7 @@ public class RenamePanel extends FileBotPanel {
|
|||
model.names().addAll(names);
|
||||
model.files().addAll(files);
|
||||
} 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;
|
||||
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import static java.lang.Math.log10;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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;
|
||||
|
||||
|
||||
public class ChecksumComputationService {
|
||||
|
||||
public static final String ACTIVE_PROPERTY = "active";
|
||||
public static final String REMAINING_TASK_COUNT_PROPERTY = "remainingTaskCount";
|
||||
public static final String TASK_COUNT_PROPERTY = "taskCount";
|
||||
|
||||
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 remainingTaskCount = 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);
|
||||
private final AtomicInteger completedTaskCount = new AtomicInteger(0);
|
||||
private final AtomicInteger totalTaskCount = new AtomicInteger(0);
|
||||
|
||||
|
||||
public ChecksumComputationService() {
|
||||
this(new DefaultThreadFactory("ChecksumComputationPool", Thread.MIN_PRIORITY));
|
||||
}
|
||||
|
||||
|
||||
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 ExecutorService newExecutor() {
|
||||
return new ChecksumComputationExecutor();
|
||||
}
|
||||
|
||||
|
||||
public void reset() {
|
||||
deactivate(true);
|
||||
}
|
||||
|
||||
|
||||
private synchronized void deactivate(boolean shutdownNow) {
|
||||
for (ChecksumComputationExecutor executor : executors.values()) {
|
||||
if (shutdownNow)
|
||||
synchronized (executors) {
|
||||
for (ExecutorService executor : executors) {
|
||||
executor.shutdownNow();
|
||||
else
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
totalTaskCount.set(0);
|
||||
completedTaskCount.set(0);
|
||||
|
||||
executors.clear();
|
||||
}
|
||||
|
||||
executors.clear();
|
||||
|
||||
activeSessionTaskCount.set(0);
|
||||
remainingTaskCount.set(0);
|
||||
fireTaskCountChanged();
|
||||
}
|
||||
|
||||
|
||||
public boolean isActive() {
|
||||
return activeSessionTaskCount.get() > 0;
|
||||
public int getTaskCount() {
|
||||
return getTotalTaskCount() - getCompletedTaskCount();
|
||||
}
|
||||
|
||||
|
||||
public int getRemainingTaskCount() {
|
||||
return remainingTaskCount.get();
|
||||
public int getTotalTaskCount() {
|
||||
return totalTaskCount.get();
|
||||
}
|
||||
|
||||
|
||||
public int getActiveSessionTaskCount() {
|
||||
return activeSessionTaskCount.get();
|
||||
public int getCompletedTaskCount() {
|
||||
return completedTaskCount.get();
|
||||
}
|
||||
|
||||
|
||||
public synchronized void purge() {
|
||||
for (ChecksumComputationExecutor executor : executors.values()) {
|
||||
executor.purge();
|
||||
public void purge() {
|
||||
synchronized (executors) {
|
||||
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 static final int MINIMUM_POOL_SIZE = 1;
|
||||
|
||||
|
||||
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 lots of files, use multiple threads
|
||||
// e.g 50 files ~ 1 thread, 1000 files ~ 3 threads, 40000 files ~ 5 threads
|
||||
int preferredPoolSize = (int) Math.max(Math.log10(getQueue().size() / 10), MINIMUM_POOL_SIZE);
|
||||
|
||||
if (getCorePoolSize() != preferredPoolSize) {
|
||||
setCorePoolSize(preferredPoolSize);
|
||||
}
|
||||
// e.g 50 files ~ 1 thread, 200 files ~ 2 threads, 1000 files ~ 3 threads, 40000 files ~ 5 threads
|
||||
return max((int) log10(getQueue().size()), 1);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
if (activeSessionTaskCount.getAndIncrement() <= 0) {
|
||||
setActive(true);
|
||||
int preferredPoolSize = getPreferredPoolSize();
|
||||
|
||||
if (getCorePoolSize() < preferredPoolSize) {
|
||||
setCorePoolSize(preferredPoolSize);
|
||||
}
|
||||
|
||||
super.execute(command);
|
||||
synchronized (this) {
|
||||
super.execute(command);
|
||||
}
|
||||
|
||||
adjustPoolSize();
|
||||
|
||||
remainingTaskCount.incrementAndGet();
|
||||
fireRemainingTaskCountChange();
|
||||
totalTaskCount.incrementAndGet();
|
||||
fireTaskCountChanged();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void purge() {
|
||||
try {
|
||||
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);
|
||||
int delta = 0;
|
||||
|
||||
if (success) {
|
||||
activeSessionTaskCount.decrementAndGet();
|
||||
|
||||
if (remainingTaskCount.decrementAndGet() <= 0) {
|
||||
setActive(false);
|
||||
}
|
||||
|
||||
fireRemainingTaskCountChange();
|
||||
synchronized (this) {
|
||||
delta += getQueue().size();
|
||||
super.purge();
|
||||
delta -= getQueue().size();
|
||||
}
|
||||
|
||||
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) {
|
||||
super.afterExecute(r, t);
|
||||
|
||||
if (remainingTaskCount.decrementAndGet() <= 0) {
|
||||
deactivate(false);
|
||||
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);
|
||||
completedTaskCount.incrementAndGet();
|
||||
fireTaskCountChanged();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void firePropertyChange(final PropertyChangeEvent evt) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
SwingWorkerPropertyChangeSupport.super.firePropertyChange(evt);
|
||||
}
|
||||
|
||||
});
|
||||
protected void terminated() {
|
||||
synchronized (executors) {
|
||||
executors.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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.FileInputStream;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.CheckedInputStream;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
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;
|
||||
|
||||
|
@ -23,37 +24,56 @@ public class ChecksumComputationTask extends SwingWorker<Long, Void> {
|
|||
|
||||
|
||||
@Override
|
||||
protected Long doInBackground() throws Exception {
|
||||
CheckedInputStream cis = new CheckedInputStream(new FileInputStream(file), new CRC32());
|
||||
protected Map<HashType, String> doInBackground() throws Exception {
|
||||
Map<HashType, Hash> hashes = new EnumMap<HashType, Hash>(HashType.class);
|
||||
|
||||
for (HashType type : HashType.values()) {
|
||||
hashes.put(type, type.newInstance());
|
||||
}
|
||||
|
||||
long length = file.length();
|
||||
|
||||
if (length > 0) {
|
||||
long done = 0;
|
||||
InputStream in = new FileInputStream(file);
|
||||
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
int bytesRead = 0;
|
||||
|
||||
while ((bytesRead = cis.read(buffer)) >= 0) {
|
||||
if (isCancelled() || Thread.currentThread().isInterrupted())
|
||||
break;
|
||||
try {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
long position = 0;
|
||||
int len = 0;
|
||||
|
||||
done += bytesRead;
|
||||
|
||||
int progress = (int) ((done * 100) / length);
|
||||
setProgress(progress);
|
||||
while ((len = in.read(buffer)) >= 0) {
|
||||
position += len;
|
||||
|
||||
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 cis.getChecksum().getValue();
|
||||
return digest(hashes);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s (%s)", getClass().getSimpleName(), file.getName());
|
||||
private Map<HashType, String> digest(Map<HashType, Hash> hashes) {
|
||||
Map<HashType, String> results = new EnumMap<HashType, String>(HashType.class);
|
||||
|
||||
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.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtilities;
|
||||
|
||||
|
@ -14,48 +18,26 @@ public class ChecksumRow {
|
|||
|
||||
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)
|
||||
*/
|
||||
private final Long embeddedChecksum;
|
||||
private String embeddedChecksum;
|
||||
|
||||
|
||||
public static enum State {
|
||||
UNKNOWN,
|
||||
OK,
|
||||
WARNING,
|
||||
ERROR,
|
||||
UNKNOWN;
|
||||
ERROR
|
||||
}
|
||||
|
||||
|
||||
public ChecksumRow(String name) {
|
||||
this.name = name;
|
||||
this.embeddedChecksum = 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;
|
||||
this.embeddedChecksum = FileBotUtilities.getEmbeddedChecksum(name);
|
||||
}
|
||||
|
||||
|
||||
|
@ -65,50 +47,91 @@ public class ChecksumRow {
|
|||
|
||||
|
||||
public State getState() {
|
||||
HashSet<Long> checksums = new HashSet<Long>();
|
||||
|
||||
for (Checksum checksum : getChecksums()) {
|
||||
if (checksum.getState() == Checksum.State.READY) {
|
||||
checksums.add(checksum.getChecksum());
|
||||
} else if (checksum.getState() == Checksum.State.ERROR) {
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
public ChecksumCell getChecksum(File root) {
|
||||
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;
|
||||
} else {
|
||||
} else if (entry.getState() != ChecksumCell.State.READY) {
|
||||
// one cell that is not ready yet -> unknown state
|
||||
return State.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
if (checksums.size() > 1) {
|
||||
// checksums do not match
|
||||
// compare hash values
|
||||
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;
|
||||
} 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) {
|
||||
return checksumMap.get(column);
|
||||
@Override
|
||||
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) {
|
||||
super.getTableCellRendererComponent(table, null, isSelected, false, row, column);
|
||||
|
||||
if (value == null)
|
||||
return this;
|
||||
|
||||
Checksum checksum = (Checksum) value;
|
||||
|
||||
switch (checksum.getState()) {
|
||||
case READY:
|
||||
setText(checksum.getChecksumString());
|
||||
return this;
|
||||
case PENDING:
|
||||
setText("Pending ...");
|
||||
return this;
|
||||
case ERROR:
|
||||
setText(checksum.getErrorMessage());
|
||||
return this;
|
||||
default:
|
||||
return progressBarRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
if (value instanceof ChecksumCell) {
|
||||
ChecksumCell checksum = (ChecksumCell) value;
|
||||
|
||||
switch (checksum.getState()) {
|
||||
case READY:
|
||||
setText(checksum.getChecksum(HashType.CRC32));
|
||||
break;
|
||||
case PENDING:
|
||||
setText("Pending ...");
|
||||
break;
|
||||
case ERROR:
|
||||
setText(checksum.getError().getMessage());
|
||||
break;
|
||||
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) {
|
||||
|
||||
Checksum checksum = (Checksum) value;
|
||||
ChecksumComputationTask task = ((ChecksumCell) value).getTask();
|
||||
|
||||
progressBar.setValue(checksum.getProgress());
|
||||
if (task != null) {
|
||||
progressBar.setValue(task.getProgress());
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
this.setBackground(table.getSelectionBackground());
|
||||
|
|
|
@ -5,11 +5,9 @@ package net.sourceforge.filebot.ui.panel.sfv;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import java.util.Formatter;
|
||||
import net.sourceforge.filebot.Settings;
|
||||
import net.sourceforge.filebot.ui.transfer.TextFileExportHandler;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
|
||||
|
@ -26,19 +24,19 @@ public class ChecksumTableExportHandler extends TextFileExportHandler {
|
|||
|
||||
@Override
|
||||
public boolean canExport() {
|
||||
return model.getRowCount() > 0 && model.getChecksumColumnCount() > 0;
|
||||
return model.getRowCount() > 0 && model.getChecksumList().size() > 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void export(PrintWriter out) {
|
||||
export(out, model.getChecksumColumn(0));
|
||||
public void export(Formatter out) {
|
||||
export(out, model.getChecksumList().get(0));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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");
|
||||
|
||||
try {
|
||||
export(out, column);
|
||||
export(new Formatter(out), column);
|
||||
} finally {
|
||||
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");
|
||||
SimpleDateFormat time = new SimpleDateFormat("HH:mm:ss");
|
||||
|
||||
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()));
|
||||
for (ChecksumRow row : model) {
|
||||
//TODO select hash type
|
||||
out.format("%s %s%n", row.getName(), row.getChecksum(column).getChecksum(HashType.CRC32));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,20 @@
|
|||
package net.sourceforge.filebot.ui.panel.sfv;
|
||||
|
||||
|
||||
import static javax.swing.event.TableModelEvent.UPDATE;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.File;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
@ -19,92 +24,86 @@ import javax.swing.table.TableModel;
|
|||
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();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
private final List<File> columns = new ArrayList<File>();
|
||||
|
||||
|
||||
@Override
|
||||
public String getColumnName(int columnIndex) {
|
||||
if (columnIndex == 0)
|
||||
return "State";
|
||||
|
||||
if (columnIndex == 1)
|
||||
return "Name";
|
||||
|
||||
if (columnIndex >= checksumColumnOffset) {
|
||||
File column = columns.get(columnIndex - checksumColumnOffset);
|
||||
|
||||
// works for files too and simply returns the name unchanged
|
||||
return FileUtilities.getFolderName(column);
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
return "State";
|
||||
case 1:
|
||||
return "Name";
|
||||
default:
|
||||
// works for files too and simply returns the name unchanged
|
||||
return FileUtilities.getFolderName(getColumnRoot(columnIndex));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int columnIndex) {
|
||||
if (columnIndex == 0)
|
||||
return ChecksumRow.State.class;
|
||||
|
||||
if (columnIndex == 1)
|
||||
return String.class;
|
||||
|
||||
if (columnIndex >= checksumColumnOffset)
|
||||
return Checksum.class;
|
||||
|
||||
return null;
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
return ChecksumRow.State.class;
|
||||
case 1:
|
||||
return String.class;
|
||||
default:
|
||||
return ChecksumCell.class;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public File getColumnRoot(int columnIndex) {
|
||||
return columns.get(columnIndex - 2);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return checksumColumnOffset + getChecksumColumnCount();
|
||||
return columns.size() + 2;
|
||||
}
|
||||
|
||||
|
||||
public int getChecksumColumnCount() {
|
||||
return columns.size();
|
||||
}
|
||||
|
||||
|
||||
public List<File> getChecksumColumns() {
|
||||
public List<File> getChecksumList() {
|
||||
return Collections.unmodifiableList(columns);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return rows.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
ChecksumRow row = rows.get(rowIndex);
|
||||
|
||||
if (columnIndex == 0)
|
||||
return row.getState();
|
||||
|
||||
if (columnIndex == 1)
|
||||
return row.getName();
|
||||
|
||||
if (columnIndex >= checksumColumnOffset) {
|
||||
File column = columns.get(columnIndex - checksumColumnOffset);
|
||||
return row.getChecksum(column);
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
return row.getState();
|
||||
case 1:
|
||||
return row.getName();
|
||||
default:
|
||||
return row.getChecksum(getColumnRoot(columnIndex));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<ChecksumRow> iterator() {
|
||||
return rows.iterator();
|
||||
}
|
||||
|
||||
|
||||
|
@ -112,7 +111,22 @@ class ChecksumTableModel extends AbstractTableModel {
|
|||
int firstRow = getRowCount();
|
||||
|
||||
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;
|
||||
|
@ -123,125 +137,142 @@ class ChecksumTableModel extends AbstractTableModel {
|
|||
}
|
||||
|
||||
|
||||
private void addChecksum(String name, Checksum checksum, File column) {
|
||||
ChecksumRow row = rowMap.get(name);
|
||||
|
||||
if (row == null) {
|
||||
row = new ChecksumRow(name);
|
||||
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();
|
||||
public void remove(int... index) {
|
||||
for (int i : index) {
|
||||
for (ChecksumCell entry : rows.get(i).values()) {
|
||||
entry.removePropertyChangeListener(progressListener);
|
||||
entry.dispose();
|
||||
}
|
||||
|
||||
rowMap.remove(row.getName());
|
||||
}
|
||||
|
||||
rows.removeAll(rowsToRemove);
|
||||
fireTableRowsDeleted(rowIndices[0], rowIndices[rowIndices.length - 1]);
|
||||
// remove rows
|
||||
rows.removeAll(index);
|
||||
|
||||
fireTableRowsDeleted(index[0], index[index.length - 1]);
|
||||
}
|
||||
|
||||
|
||||
public void clear() {
|
||||
columns.clear();
|
||||
rows.clear();
|
||||
rowMap.clear();
|
||||
|
||||
fireTableStructureChanged();
|
||||
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
|
||||
public File getChecksumColumn(int columnIndex) {
|
||||
return columns.get(columnIndex);
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Checksum> getChecksumColumn(File column) {
|
||||
LinkedHashMap<String, Checksum> checksumMap = new LinkedHashMap<String, Checksum>();
|
||||
private final PropertyChangeListener progressListener = new PropertyChangeListener() {
|
||||
|
||||
for (ChecksumRow row : rows) {
|
||||
Checksum checksum = row.getChecksum(column);
|
||||
|
||||
if ((checksum != null) && (checksum.getState() == Checksum.State.READY)) {
|
||||
checksumMap.put(row.getName(), checksum);
|
||||
}
|
||||
}
|
||||
private final MutableTableModelEvent mutableUpdateEvent = new MutableTableModelEvent(ChecksumTableModel.this, UPDATE);
|
||||
|
||||
return checksumMap;
|
||||
}
|
||||
|
||||
private final PropertyChangeListener checksumListener = new PropertyChangeListener() {
|
||||
|
||||
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;
|
||||
private final Checksum checksum;
|
||||
private final File column;
|
||||
|
||||
|
||||
public ChecksumCell(String name, Checksum checksum, File column) {
|
||||
this.name = name;
|
||||
this.checksum = checksum;
|
||||
this.column = column;
|
||||
public MutableTableModelEvent(TableModel source, int type) {
|
||||
super(source, 0, 0, ALL_COLUMNS, type);
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
public Checksum getChecksum() {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
|
||||
public File getColumn() {
|
||||
return column;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
public MutableTableModelEvent setRow(int row) {
|
||||
this.firstRow = row;
|
||||
this.lastRow = row;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
super(source);
|
||||
type = CHECKSUM_PROGRESS;
|
||||
public abstract K key(V value);
|
||||
|
||||
|
||||
@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.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.TitledBorder;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.ui.FileBotPanel;
|
||||
import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
|
||||
import net.sourceforge.filebot.ui.SelectDialog;
|
||||
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
||||
import net.sourceforge.filebot.ui.transfer.SaveAction;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
import net.sourceforge.tuned.MessageHandler;
|
||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||
|
||||
|
@ -82,7 +77,8 @@ public class SfvPanel extends FileBotPanel {
|
|||
|
||||
int row = sfvTable.getSelectionModel().getMinSelectionIndex();
|
||||
|
||||
sfvTable.removeRows(sfvTable.getSelectedRows());
|
||||
// remove selected rows
|
||||
sfvTable.getModel().remove(sfvTable.getSelectedRows());
|
||||
|
||||
int maxRow = sfvTable.getRowCount() - 1;
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import javax.swing.table.TableColumn;
|
|||
import javax.swing.table.TableModel;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtilities;
|
||||
import net.sourceforge.filebot.ui.panel.sfv.ChecksumTableModel.ChecksumTableModelEvent;
|
||||
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
||||
|
||||
|
||||
|
@ -27,7 +26,6 @@ class SfvTable extends JTable {
|
|||
|
||||
|
||||
public SfvTable() {
|
||||
|
||||
transferablePolicy = new SfvTransferablePolicy(getModel(), checksumComputationService);
|
||||
exportHandler = new ChecksumTableExportHandler(getModel());
|
||||
|
||||
|
@ -48,7 +46,7 @@ class SfvTable extends JTable {
|
|||
// highlight CRC32 patterns in filenames in green and with smaller font-size
|
||||
setDefaultRenderer(String.class, new HighlightPatternCellRenderer(FileBotUtilities.EMBEDDED_CHECKSUM_PATTERN, "#009900", "smaller"));
|
||||
setDefaultRenderer(ChecksumRow.State.class, new StateIconTableCellRenderer());
|
||||
setDefaultRenderer(Checksum.class, new ChecksumTableCellRenderer());
|
||||
setDefaultRenderer(ChecksumCell.class, new ChecksumTableCellRenderer());
|
||||
}
|
||||
|
||||
|
||||
|
@ -91,6 +89,7 @@ class SfvTable extends JTable {
|
|||
|
||||
for (int i = 0; i < getColumnCount(); i++) {
|
||||
TableColumn column = getColumnModel().getColumn(i);
|
||||
|
||||
if (i == 0) {
|
||||
column.setPreferredWidth(45);
|
||||
} else if (i == 1) {
|
||||
|
@ -110,26 +109,15 @@ class SfvTable extends JTable {
|
|||
}
|
||||
|
||||
|
||||
public void removeRows(int... rowIndices) {
|
||||
getModel().removeRows(rowIndices);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void tableChanged(TableModelEvent e) {
|
||||
// only request repaint when progress changes. Selection will go haywire if you don't.
|
||||
if (e.getType() == ChecksumTableModelEvent.CHECKSUM_PROGRESS) {
|
||||
repaint();
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO CCS in SfvPanel??
|
||||
if (e.getType() == TableModelEvent.DELETE) {
|
||||
// remove cancelled tasks from queue
|
||||
checksumComputationService.purge();
|
||||
}
|
||||
|
||||
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.tuned.FileUtilities.containsOnly;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -19,14 +21,14 @@ import java.util.regex.Pattern;
|
|||
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;
|
||||
|
||||
|
||||
public SfvTransferablePolicy(ChecksumTableModel tableModel, ChecksumComputationService checksumComputationService) {
|
||||
this.tableModel = tableModel;
|
||||
public SfvTransferablePolicy(ChecksumTableModel model, ChecksumComputationService checksumComputationService) {
|
||||
this.model = model;
|
||||
this.checksumComputationService = checksumComputationService;
|
||||
}
|
||||
|
||||
|
@ -40,46 +42,58 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
|
|||
@Override
|
||||
protected void clear() {
|
||||
checksumComputationService.reset();
|
||||
tableModel.clear();
|
||||
model.clear();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void process(List<ChecksumTableModel.ChecksumCell> chunks) {
|
||||
tableModel.addAll(chunks);
|
||||
protected void process(List<ChecksumCell> chunks) {
|
||||
model.addAll(chunks);
|
||||
}
|
||||
|
||||
|
||||
protected void loadSfvFile(File sfvFile) {
|
||||
protected void loadSfvFile(File sfvFile, Executor executor) {
|
||||
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;
|
||||
Pattern pattern = Pattern.compile("(.*)\\s+(\\p{XDigit}{8})");
|
||||
|
||||
while (((line = in.readLine()) != null) && !Thread.interrupted()) {
|
||||
if (line.startsWith(";"))
|
||||
continue;
|
||||
try {
|
||||
Pattern pattern = Pattern.compile("(.+)\\s+(\\p{XDigit}{8})");
|
||||
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (!matcher.matches())
|
||||
continue;
|
||||
|
||||
String filename = matcher.group(1);
|
||||
String checksumString = matcher.group(2);
|
||||
|
||||
publish(new ChecksumTableModel.ChecksumCell(filename, new Checksum(checksumString), sfvFile));
|
||||
|
||||
File column = sfvFile.getParentFile();
|
||||
File file = new File(column, filename);
|
||||
|
||||
if (file.exists()) {
|
||||
publish(new ChecksumTableModel.ChecksumCell(filename, checksumComputationService.schedule(file, column), column));
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
|
||||
if (line.startsWith(";"))
|
||||
continue;
|
||||
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (!matcher.matches())
|
||||
continue;
|
||||
|
||||
String filename = matcher.group(1);
|
||||
String checksum = matcher.group(2);
|
||||
|
||||
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) {
|
||||
// should not happen
|
||||
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
|
||||
|
@ -95,44 +109,52 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
|
|||
|
||||
@Override
|
||||
protected void load(List<File> files) {
|
||||
ExecutorService executor = checksumComputationService.newExecutor();
|
||||
|
||||
try {
|
||||
if (containsOnly(files, SFV_FILES)) {
|
||||
// one or more sfv files
|
||||
for (File file : files) {
|
||||
loadSfvFile(file);
|
||||
loadSfvFile(file, executor);
|
||||
}
|
||||
} else if ((files.size() == 1) && files.get(0).isDirectory()) {
|
||||
// one single folder
|
||||
File file = files.get(0);
|
||||
|
||||
for (File f : file.listFiles()) {
|
||||
load(f, file, "");
|
||||
load(f, file, "", executor);
|
||||
}
|
||||
} else {
|
||||
// bunch of files
|
||||
for (File f : files) {
|
||||
load(f, f.getParentFile(), "");
|
||||
load(f, f.getParentFile(), "", executor);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// supposed to happen if background execution was aborted
|
||||
} finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void load(File file, File column, String prefix) throws InterruptedException {
|
||||
protected void load(File file, File root, String prefix, Executor executor) throws InterruptedException {
|
||||
if (Thread.interrupted())
|
||||
throw new InterruptedException();
|
||||
|
||||
if (file.isDirectory()) {
|
||||
// load all files in the file tree
|
||||
String newPrefix = prefix + file.getName() + "/";
|
||||
|
||||
for (File f : file.listFiles()) {
|
||||
load(f, column, newPrefix);
|
||||
load(f, root, newPrefix, executor);
|
||||
}
|
||||
} 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.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JTable;
|
||||
|
@ -10,17 +12,20 @@ import javax.swing.SwingConstants;
|
|||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.ui.panel.sfv.ChecksumRow.State;
|
||||
|
||||
|
||||
class StateIconTableCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
private Icon warning = ResourceManager.getIcon("status.warning");
|
||||
private Icon error = ResourceManager.getIcon("status.error");
|
||||
private Icon unknown = ResourceManager.getIcon("status.unknown");
|
||||
private Icon ok = ResourceManager.getIcon("status.ok");
|
||||
private final Map<State, Icon> icons = new EnumMap<State, Icon>(State.class);
|
||||
|
||||
|
||||
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);
|
||||
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) {
|
||||
super.getTableCellRendererComponent(table, null, isSelected, false, row, column);
|
||||
|
||||
ChecksumRow.State state = (ChecksumRow.State) value;
|
||||
|
||||
switch (state) {
|
||||
case OK:
|
||||
setIcon(ok);
|
||||
break;
|
||||
case ERROR:
|
||||
setIcon(error);
|
||||
break;
|
||||
case WARNING:
|
||||
setIcon(warning);
|
||||
break;
|
||||
case UNKNOWN:
|
||||
setIcon(unknown);
|
||||
break;
|
||||
}
|
||||
setIcon(icons.get(value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
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.PropertyChangeListener;
|
||||
|
||||
|
@ -9,6 +11,8 @@ import javax.swing.BorderFactory;
|
|||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JProgressBar;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
|
||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||
|
||||
|
@ -19,13 +23,13 @@ class TotalProgressPanel extends Box {
|
|||
|
||||
private final JProgressBar progressBar = new JProgressBar(0, 0);
|
||||
|
||||
private final ChecksumComputationService checksumComputationService;
|
||||
private final ChecksumComputationService service;
|
||||
|
||||
|
||||
public TotalProgressPanel(ChecksumComputationService checksumComputationService) {
|
||||
super(BoxLayout.Y_AXIS);
|
||||
|
||||
this.checksumComputationService = checksumComputationService;
|
||||
this.service = checksumComputationService;
|
||||
|
||||
// invisible by default
|
||||
setVisible(false);
|
||||
|
@ -38,40 +42,49 @@ class TotalProgressPanel extends Box {
|
|||
|
||||
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) {
|
||||
final int completedTaskCount = service.getCompletedTaskCount();
|
||||
final int totalTaskCount = service.getTotalTaskCount();
|
||||
|
||||
String property = evt.getPropertyName();
|
||||
|
||||
if (property == ChecksumComputationService.ACTIVE_PROPERTY) {
|
||||
Boolean active = (Boolean) evt.getNewValue();
|
||||
// invoke on EDT
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
|
||||
if (active) {
|
||||
TunedUtilities.invokeLater(millisToSetVisible, new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
setVisible(checksumComputationService.isActive());
|
||||
@Override
|
||||
public void run() {
|
||||
if (completedTaskCount < totalTaskCount) {
|
||||
if (setVisibleTimer == null) {
|
||||
setVisibleTimer = TunedUtilities.invokeLater(millisToSetVisible, new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
setVisible(service.getTaskCount() > service.getCompletedTaskCount());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// hide when not active
|
||||
setVisible(false);
|
||||
}
|
||||
} else if (property == ChecksumComputationService.REMAINING_TASK_COUNT_PROPERTY) {
|
||||
|
||||
int taskCount = checksumComputationService.getActiveSessionTaskCount();
|
||||
int progress = taskCount - checksumComputationService.getRemainingTaskCount();
|
||||
|
||||
progressBar.setValue(progress);
|
||||
progressBar.setMaximum(taskCount);
|
||||
|
||||
progressBar.setString(progressBar.getValue() + " / " + progressBar.getMaximum());
|
||||
}
|
||||
} else {
|
||||
if (setVisibleTimer != null) {
|
||||
setVisibleTimer.stop();
|
||||
setVisibleTimer = null;
|
||||
}
|
||||
|
||||
// hide when not active
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
progressBar.setValue(completedTaskCount);
|
||||
progressBar.setMaximum(totalTaskCount);
|
||||
|
||||
progressBar.setString(completedTaskCount + " / " + totalTaskCount);
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import java.util.EventListener;
|
|||
|
||||
import javax.swing.JComponent;
|
||||
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.EventList;
|
||||
|
@ -24,7 +25,33 @@ public class SubtitlePackagePanel extends JComponent {
|
|||
|
||||
public SubtitlePackagePanel() {
|
||||
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());
|
||||
|
||||
JList list = new JList(new EventListModel<SubtitlePackage>(observableList));
|
||||
|
||||
list.setCellRenderer(new SubtitleCellRenderer());
|
||||
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
|
||||
list.setVisibleRowCount(-1);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -51,9 +82,14 @@ public class SubtitlePackagePanel extends JComponent {
|
|||
private ObservableElementList<SubtitlePackage> list = null;
|
||||
|
||||
|
||||
public EventListener installListener(SubtitlePackage element) {
|
||||
PropertyChangeListener listener = new SubtitlePackageListener(element);
|
||||
element.getDownloadTask().addPropertyChangeListener(listener);
|
||||
public EventListener installListener(final SubtitlePackage element) {
|
||||
PropertyChangeListener listener = new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
list.elementChanged(element);
|
||||
}
|
||||
};
|
||||
|
||||
return listener;
|
||||
}
|
||||
|
@ -64,24 +100,10 @@ public class SubtitlePackagePanel extends JComponent {
|
|||
}
|
||||
|
||||
|
||||
public void setObservableElementList(ObservableElementList<SubtitlePackage> list) {
|
||||
this.list = 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);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void setObservableElementList(ObservableElementList<? extends SubtitlePackage> list) {
|
||||
this.list = (ObservableElementList<SubtitlePackage>) list;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import java.beans.PropertyChangeListener;
|
|||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -87,8 +89,13 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
|
|||
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() {
|
||||
load(files);
|
||||
protected Void doInBackground() {
|
||||
try {
|
||||
load(files);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("global").log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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
|
||||
|
|
|
@ -6,7 +6,7 @@ import java.awt.datatransfer.Transferable;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Formatter;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.TransferHandler;
|
||||
|
@ -17,7 +17,7 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
|
|||
public abstract boolean canExport();
|
||||
|
||||
|
||||
public abstract void export(PrintWriter out);
|
||||
public abstract void export(Formatter out);
|
||||
|
||||
|
||||
public abstract String getDefaultFileName();
|
||||
|
@ -28,7 +28,7 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
|
|||
PrintWriter out = new PrintWriter(file, "UTF-8");
|
||||
|
||||
try {
|
||||
export(out);
|
||||
export(new Formatter(out));
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
|
|||
@Override
|
||||
public Transferable createTransferable(JComponent c) {
|
||||
// get transfer data
|
||||
StringWriter buffer = new StringWriter();
|
||||
export(new PrintWriter(buffer));
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
export(new Formatter(buffer));
|
||||
|
||||
return new LazyTextFileTransferable(buffer.toString(), getDefaultFileName());
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import java.net.URI;
|
|||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -135,7 +134,7 @@ public class AnidbClient implements EpisodeListClient {
|
|||
|
||||
|
||||
@Override
|
||||
public Collection<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception {
|
||||
public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,23 +3,23 @@ package net.sourceforge.filebot.web;
|
|||
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
|
||||
public interface EpisodeListClient {
|
||||
|
||||
public Collection<SearchResult> search(String query) throws Exception;
|
||||
public List<SearchResult> search(String query) throws Exception;
|
||||
|
||||
|
||||
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);
|
||||
|
|
|
@ -3,7 +3,7 @@ package net.sourceforge.filebot.web;
|
|||
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
@ -11,10 +11,10 @@ import javax.swing.Icon;
|
|||
|
||||
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);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
package net.sourceforge.tuned;
|
||||
|
||||
|
||||
public final class ExceptionUtil {
|
||||
public final class ExceptionUtilities {
|
||||
|
||||
public static Throwable getRootCause(Throwable t) {
|
||||
while (t.getCause() != null) {
|
||||
|
@ -25,7 +25,7 @@ public final class ExceptionUtil {
|
|||
/**
|
||||
* Dummy constructor to prevent instantiation.
|
||||
*/
|
||||
private ExceptionUtil() {
|
||||
private ExceptionUtilities() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
@ -282,7 +282,7 @@ public class PreferencesMap<T> implements Map<String, T> {
|
|||
return constructor.newInstance(stringValue);
|
||||
} catch (InvocationTargetException e) {
|
||||
// try to throw the cause directly, e.g. NumberFormatException
|
||||
throw ExceptionUtil.asRuntimeException(e.getCause());
|
||||
throw ExceptionUtilities.asRuntimeException(e.getCause());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.util.Arrays;
|
|||
|
||||
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 {
|
||||
return (String) getTextMethod.invoke(value);
|
||||
} catch (Exception e) {
|
||||
throw ExceptionUtil.asRuntimeException(e);
|
||||
throw ExceptionUtilities.asRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ public class SimpleLabelProvider<T> implements LabelProvider<T> {
|
|||
try {
|
||||
return (Icon) getIconMethod.invoke(value);
|
||||
} 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.Timer;
|
||||
|
||||
import net.sourceforge.tuned.ExceptionUtil;
|
||||
import net.sourceforge.tuned.ExceptionUtilities;
|
||||
|
||||
|
||||
public final class TunedUtilities {
|
||||
|
@ -120,7 +120,7 @@ public final class TunedUtilities {
|
|||
|
||||
return listener;
|
||||
} catch (Exception e) {
|
||||
throw ExceptionUtil.asRuntimeException(e);
|
||||
throw ExceptionUtilities.asRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ public final class TunedUtilities {
|
|||
try {
|
||||
firePropertyChange.invoke(target, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
|
||||
} catch (Exception e) {
|
||||
throw ExceptionUtil.asRuntimeException(e);
|
||||
throw ExceptionUtilities.asRuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue