* 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:
Reinhard Pointner 2009-02-09 20:56:20 +00:00
parent 684a7512bc
commit 5674173417
36 changed files with 902 additions and 789 deletions

Binary file not shown.

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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());

View File

@ -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() {

View File

@ -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);
}
}
};

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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());

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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()));
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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);
};
});
}
};

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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());
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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);

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}