* improved BackgroundTransferablePolicy

* improved ChecksumComputationService
* (... both don't "leak" threads anymore)
This commit is contained in:
Reinhard Pointner 2008-03-27 00:28:06 +00:00
parent ee6dc82d50
commit 2026c60b1d
15 changed files with 220 additions and 188 deletions

View File

@ -108,12 +108,7 @@ public class FileFormat {
if (file.isDirectory())
return "Folder";
return getFileType(file.getName());
}
public static String getFileType(String name) {
String extension = getExtension(name, false);
String extension = getExtension(file.getName(), false);
if (!extension.isEmpty())
return extension;

View File

@ -10,6 +10,7 @@ import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
@ -31,7 +32,7 @@ class FileTree extends FileBotTree {
public FileTree() {
FileTreeTransferPolicy transferPolicy = new FileTreeTransferPolicy(this);
FileTreeTransferablePolicy transferPolicy = new FileTreeTransferablePolicy(this);
transferPolicy.addPropertyChangeListener(LOADING_PROPERTY, new LoadingPropertyChangeListener());
setTransferablePolicy(transferPolicy);
@ -71,16 +72,20 @@ class FileTree extends FileBotTree {
@Override
public void clear() {
FileTreeTransferPolicy transferPolicy = ((FileTreeTransferPolicy) getTransferablePolicy());
boolean loading = transferPolicy.isActive();
((FileTreeTransferablePolicy) getTransferablePolicy()).reset();
if (loading) {
transferPolicy.cancelAll();
}
// there may still be some runnables from the transfer in the event queue,
// clear the model, after those runnables have finished,
// otherwise it may happen, that stuff is added, after the model has been cleared
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
FileTree.super.clear();
contentChanged();
}
});
super.clear();
contentChanged();
}

View File

@ -6,17 +6,18 @@ import java.io.File;
import java.util.List;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.ui.transferablepolicies.BackgroundFileTransferablePolicy;
class FileTreeTransferPolicy extends BackgroundFileTransferablePolicy<DefaultMutableTreeNode> {
class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<DefaultMutableTreeNode> {
private FileTree tree;
private final FileTree tree;
public FileTreeTransferPolicy(FileTree tree) {
public FileTreeTransferablePolicy(FileTree tree) {
this.tree = tree;
}
@ -35,18 +36,26 @@ class FileTreeTransferPolicy extends BackgroundFileTransferablePolicy<DefaultMut
@Override
protected void process(List<DefaultMutableTreeNode> chunks) {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
for (DefaultMutableTreeNode node : chunks) {
root.add(node);
}
model.reload(root);
}
@Override
protected void load(List<File> files) {
for (File file : files) {
publish(getTree(file));
DefaultMutableTreeNode node = getTree(file);
if (Thread.currentThread().isInterrupted())
return;
publish(node);
}
}

View File

@ -19,7 +19,7 @@ import javax.swing.ListModel;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import net.sourceforge.filebot.ui.panel.rename.entry.AbstractFileEntry;
import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry;
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
@ -50,8 +50,8 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
public void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if ((list.getModel() == files) && (value instanceof AbstractFileEntry<?>)) {
AbstractFileEntry<?> entry = (AbstractFileEntry<?>) value;
if ((list.getModel() == files) && (value instanceof FileEntry)) {
FileEntry entry = (FileEntry) value;
extension.setText(entry.getType());
extension.setVisible(true);
@ -86,7 +86,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
private class ExtensionLabel extends JLabel {
private final Insets margin = new Insets(0, 10, 0, 0);
private final Insets padding = new Insets(1, 6, 1, 5);
private final Insets padding = new Insets(0, 6, 0, 5);
private final int arc = 10;
private Color gradientBeginColor = new Color(0xFFCC00);
@ -97,7 +97,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
public ExtensionLabel() {
setOpaque(false);
setForeground(new Color(42, 42, 42));
setForeground(new Color(0x141414));
setBorder(new CompoundBorder(new EmptyBorder(margin), new EmptyBorder(padding)));
}
@ -120,7 +120,6 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
g2d.setPaint(getForeground());
Rectangle2D textBounds = g2d.getFontMetrics().getStringBounds(getText(), g2d);
g2d.drawString(getText(), (float) (shape.getCenterX() - textBounds.getX() - (textBounds.getWidth() / 2f)), (float) (shape.getCenterY() - textBounds.getY() - (textBounds.getHeight() / 2)));
}

View File

@ -4,25 +4,11 @@ package net.sourceforge.filebot.ui.panel.rename.entry;
public abstract class AbstractFileEntry<T> extends ListEntry<T> {
private final long length;
private final String type;
public AbstractFileEntry(String name, T value, String type, long length) {
public AbstractFileEntry(String name, T value) {
super(name, value);
this.length = length;
this.type = type;
}
public long getLength() {
return length;
}
public String getType() {
return type;
}
public abstract long getLength();
}

View File

@ -10,7 +10,18 @@ import net.sourceforge.filebot.FileFormat;
public class FileEntry extends AbstractFileEntry<File> {
public FileEntry(File file) {
super(FileFormat.getFileName(file), file, FileFormat.getFileType(file), file.length());
super(FileFormat.getFileName(file), file);
}
@Override
public long getLength() {
return getValue().length();
}
public String getType() {
return FileFormat.getFileType(getValue());
}
}

View File

@ -10,6 +10,13 @@ import net.sourceforge.filebot.torrent.Torrent.Entry;
public class TorrentEntry extends AbstractFileEntry<Torrent.Entry> {
public TorrentEntry(Entry value) {
super(FileFormat.getNameWithoutExtension(value.getName()), value, FileFormat.getFileType(value.getName()), value.getLength());
super(FileFormat.getNameWithoutExtension(value.getName()), value);
}
@Override
public long getLength() {
return getValue().getLength();
}
}

View File

@ -43,9 +43,8 @@ public class Checksum {
}
public Checksum(ChecksumComputationTask computationTask) {
protected Checksum(ChecksumComputationTask computationTask) {
this.computationTask = computationTask;
this.computationTask.addPropertyChangeListener(new ComputationTaskPropertyChangeListener());
}

View File

@ -18,12 +18,16 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.SwingUtilities;
import net.sourceforge.tuned.DefaultThreadFactory;
public class ChecksumComputationService {
public static final String ACTIVE_PROPERTY = "ACTIVE_PROPERTY";
public static final String REMAINING_TASK_COUNT_PROPERTY = "REMAINING_TASK_COUNT_PROPERTY";
private static final ThreadFactory checksumComputationThreadFactory = new DefaultThreadFactory("ChecksumComputationPool", Thread.MIN_PRIORITY);
private static final ChecksumComputationService service = new ChecksumComputationService();
@ -44,22 +48,35 @@ public class ChecksumComputationService {
}
public ChecksumComputationTask submit(File file, File workerQueueKey) {
public Checksum getChecksum(File file, File workerQueueKey) {
ChecksumComputationTask task = new ChecksumComputationTask(file);
Checksum checksum = new Checksum(task);
getExecutor(workerQueueKey).execute(task);
return task;
return checksum;
}
public void cancelAll() {
public void reset() {
deactivate(true);
}
private void deactivate(boolean shutdownNow) {
synchronized (executors) {
for (ChecksumComputationExecutor executor : executors.values()) {
executor.shutdownNow();
if (shutdownNow) {
executor.shutdownNow();
} else {
executor.shutdown();
}
}
setActive(false);
executors.clear();
activeSessionTaskCount.set(0);
remainingTaskCount.set(0);
}
}
@ -108,7 +125,7 @@ public class ChecksumComputationService {
public ChecksumComputationExecutor() {
super(MINIMUM_POOL_SIZE, MINIMUM_POOL_SIZE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ChecksumComputationThreadFactory());
super(MINIMUM_POOL_SIZE, MINIMUM_POOL_SIZE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), checksumComputationThreadFactory);
}
@ -191,6 +208,7 @@ public class ChecksumComputationService {
super.afterExecute(r, t);
if (remainingTaskCount.decrementAndGet() <= 0) {
deactivate(false);
setActive(false);
}
@ -200,15 +218,6 @@ public class ChecksumComputationService {
private void setActive(boolean active) {
if (!active) {
activeSessionTaskCount.set(0);
remainingTaskCount.set(0);
synchronized (executors) {
executors.clear();
}
}
SwingUtilities.invokeLater(new FirePropertyChangeRunnable(ACTIVE_PROPERTY, active));
}
@ -231,46 +240,18 @@ public class ChecksumComputationService {
private class FirePropertyChangeRunnable implements Runnable {
private final String property;
private final Object oldValue;
private final Object newValue;
public FirePropertyChangeRunnable(String property, Object oldValue, Object newValue) {
this.property = property;
this.oldValue = oldValue;
this.newValue = newValue;
}
public FirePropertyChangeRunnable(String property, Object newValue) {
this(property, null, newValue);
this.property = property;
this.newValue = newValue;
}
@Override
public void run() {
propertyChangeSupport.firePropertyChange(property, oldValue, newValue);
}
}
private static class ChecksumComputationThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(0);
private final ThreadGroup group = new ThreadGroup("ChecksumComputationPool");
private final AtomicInteger threadNumber = new AtomicInteger(0);
private final String namePrefix = String.format("%s-%d-thread-", group.getName(), poolNumber.incrementAndGet());
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.incrementAndGet(), 0);
t.setDaemon(false);
t.setPriority(Thread.MIN_PRIORITY);
return t;
propertyChangeSupport.firePropertyChange(property, null, newValue);
}
}

View File

@ -147,15 +147,7 @@ class ChecksumTableModel extends AbstractTableModel {
public synchronized void clear() {
// stop any running computations
for (ChecksumRow row : rows) {
for (Checksum checksum : row.getChecksums()) {
checksum.cancelComputationTask();
}
}
ChecksumComputationService.getService().cancelAll();
ChecksumComputationService.getService().reset();
checksumColumnRoots.clear();
rows.clear();

View File

@ -12,6 +12,7 @@ import java.util.logging.Logger;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
@ -90,9 +91,18 @@ class SfvTable extends JTable implements TransferablePolicySupport, Saveable {
public void clear() {
((BackgroundFileTransferablePolicy<?>) getTransferablePolicy()).cancelAll();
((BackgroundFileTransferablePolicy<?>) getTransferablePolicy()).reset();
((ChecksumTableModel) getModel()).clear();
// there may still be some runnables from the transfer in the event queue,
// clear the model, after those runnables have finished,
// otherwise it may happen, that stuff is added, after the model has been cleared
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
((ChecksumTableModel) getModel()).clear();
}
});
}

View File

@ -35,7 +35,6 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
@Override
protected void clear() {
cancelAll();
tableModel.clear();
}
@ -71,7 +70,7 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
File compareFile = new File(compareColumnRoot, filename);
if (compareFile.exists()) {
publish(new ChecksumTableModel.Entry(filename, new Checksum(ChecksumComputationService.getService().submit(compareFile, compareColumnRoot)), compareColumnRoot));
publish(new ChecksumTableModel.Entry(filename, ChecksumComputationService.getService().getChecksum(compareFile, compareColumnRoot), compareColumnRoot));
}
}
@ -123,8 +122,7 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
load(f, columnRoot, newPrefix);
}
} else if (file.isFile()) {
publish(new ChecksumTableModel.Entry(prefix + file.getName(), new Checksum(ChecksumComputationService.getService().submit(file, columnRoot)), columnRoot));
publish(new ChecksumTableModel.Entry(prefix + file.getName(), ChecksumComputationService.getService().getChecksum(file, columnRoot), columnRoot));
}
}
}

View File

@ -9,19 +9,26 @@ import java.io.File;
import java.util.Arrays;
import java.util.List;
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 javax.swing.SwingUtilities;
import net.sourceforge.tuned.DefaultThreadFactory;
public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferablePolicy {
public static final String LOADING_PROPERTY = "loading";
private static final ThreadFactory backgroundTransferThreadFactory = new DefaultThreadFactory("BackgroundTransferPool", Thread.NORM_PRIORITY);
private SingleThreadExecutor executor = null;
private final AtomicInteger count = new AtomicInteger(0);
@Override
public void handleTransferable(Transferable tr, boolean add) {
@ -33,39 +40,54 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
if (!add)
clear();
submit(new LoadFilesTask(files));
execute(new LoadFilesTask(files));
}
protected void submit(Runnable task) {
synchronized (this) {
if (executor == null) {
executor = new SingleThreadExecutor();
}
protected synchronized void execute(Runnable task) {
if (executor == null) {
executor = new SingleThreadExecutor();
}
executor.submit(task);
executor.execute(task);
}
public boolean isActive() {
synchronized (this) {
if (executor == null)
return false;
return executor.isActive();
}
return count.get() > 0;
}
public void cancelAll() {
synchronized (this) {
if (executor != null) {
// interrupt all threads
executor.shutdownNow();
executor = null;
private synchronized void setActive(final boolean active) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
propertyChangeSupport.firePropertyChange(LOADING_PROPERTY, null, active);
}
});
}
private synchronized void deactivate(boolean shutdownNow) {
if (executor != null) {
if (shutdownNow) {
executor.shutdownNow();
} else {
executor.shutdown();
}
executor = null;
}
count.set(0);
}
public synchronized void reset() {
deactivate(true);
setActive(false);
}
@ -75,7 +97,7 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
* @param chunks
*/
protected final void publish(V... chunks) {
SwingUtilities.invokeLater(new ProcessChunksTask(chunks, Thread.currentThread()));
SwingUtilities.invokeLater(new ProcessChunksTask(chunks));
}
@ -90,7 +112,7 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
private class LoadFilesTask implements Runnable {
private List<File> files;
private final List<File> files;
public LoadFilesTask(List<File> files) {
@ -107,76 +129,21 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
private class ProcessChunksTask implements Runnable {
private V[] chunks;
private Thread publisher;
private final V[] chunks;
public ProcessChunksTask(V[] chunks, Thread publisher) {
public ProcessChunksTask(V[] chunks) {
this.chunks = chunks;
this.publisher = publisher;
}
@Override
public void run() {
if (!publisher.isInterrupted() && publisher.isAlive()) {
process(Arrays.asList(chunks));
}
process(Arrays.asList(chunks));
}
}
private class SingleThreadExecutor extends ThreadPoolExecutor {
private final AtomicInteger count = new AtomicInteger(0);
public SingleThreadExecutor() {
super(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public boolean isActive() {
return count.get() > 0;
}
@Override
public void execute(Runnable command) {
if (count.getAndIncrement() <= 0) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
propertyChangeSupport.firePropertyChange(LOADING_PROPERTY, false, true);
}
});
}
super.execute(command);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (count.decrementAndGet() <= 0) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
propertyChangeSupport.firePropertyChange(LOADING_PROPERTY, true, false);
}
});
}
}
}
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
@ -188,4 +155,36 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
}
private class SingleThreadExecutor extends ThreadPoolExecutor {
public SingleThreadExecutor() {
super(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), backgroundTransferThreadFactory);
}
@Override
public void execute(Runnable command) {
if (count.getAndIncrement() <= 0) {
setActive(true);
}
super.execute(command);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (count.decrementAndGet() <= 0) {
// shutdown executor
deactivate(false);
setActive(false);
}
}
}
}

View File

@ -9,7 +9,7 @@ import java.nio.ByteBuffer;
public class ByteBufferInputStream extends InputStream {
private ByteBuffer buffer;
private final ByteBuffer buffer;
public ByteBufferInputStream(ByteBuffer buffer) {

View File

@ -0,0 +1,41 @@
package net.sourceforge.tuned;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class DefaultThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(0);
private final ThreadGroup group;
private final int priority;
private final boolean daemon;
public DefaultThreadFactory(String name, int priority) {
this(name, priority, false);
}
public DefaultThreadFactory(String groupName, int priority, boolean daemon) {
group = new ThreadGroup(groupName);
group.setDaemon(daemon);
this.daemon = daemon;
this.priority = priority;
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, String.format("%s-thread-%d", group.getName(), threadNumber.incrementAndGet()));
t.setDaemon(daemon);
t.setPriority(priority);
return t;
}
}