package net.filebot.ui.transfer; import static net.filebot.ui.transfer.FileTransferable.*; import java.awt.datatransfer.Transferable; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.util.ArrayList; import java.util.List; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.event.SwingPropertyChangeSupport; public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferablePolicy { public static final String LOADING_PROPERTY = "loading"; private final ThreadLocal<BackgroundWorker> threadLocalWorker = new ThreadLocal<BackgroundWorker>(); private final List<BackgroundWorker> workers = new ArrayList<BackgroundWorker>(2); @Override public void handleTransferable(Transferable tr, TransferAction action) throws Exception { List<File> files = getFilesFromTransferable(tr); if (action != TransferAction.ADD) { clear(); } handleInBackground(files, action); } protected void handleInBackground(List<File> files, TransferAction action) { new BackgroundWorker(files, action).execute(); } @Override protected void clear() { // stop other workers on clear (before starting new worker) reset(); } public void reset() { synchronized (workers) { if (workers.size() > 0) { // avoid ConcurrentModificationException by iterating over a copy for (BackgroundWorker worker : new ArrayList<BackgroundWorker>(workers)) { // worker.cancel() will invoke worker.done() which will invoke workers.remove(worker) worker.cancel(true); } } } } public boolean isLoading() { synchronized (workers) { return !workers.isEmpty(); } } protected abstract void process(List<V> chunks); protected abstract void process(Exception exception); protected final void publish(V[] chunks) { BackgroundWorker worker = threadLocalWorker.get(); if (worker == null) { // fail if a non-background-worker thread is trying to access the thread-local worker object throw new IllegalThreadStateException("Illegal access thread"); } worker.offer(chunks); } protected final void publish(Exception exception) { SwingUtilities.invokeLater(() -> process(exception)); } protected class BackgroundWorker extends SwingWorker<Object, V> { private final List<File> files; private final TransferAction action; public BackgroundWorker(List<File> files, TransferAction action) { this.files = files; this.action = action; // register this worker synchronized (workers) { if (workers.add(this) && workers.size() == 1) { swingPropertyChangeSupport.firePropertyChange(LOADING_PROPERTY, false, true); } } } @Override protected Object doInBackground() throws Exception { // associate this worker with the current (background) thread threadLocalWorker.set(this); try { load(files, action); } finally { threadLocalWorker.remove(); } return null; } public void offer(V[] chunks) { if (!isCancelled()) { publish(chunks); } } @Override protected void process(List<V> chunks) { if (!isCancelled()) { BackgroundFileTransferablePolicy.this.process(chunks); } } @Override protected void done() { if (!isCancelled()) { try { // check for exception get(); } catch (Exception e) { BackgroundFileTransferablePolicy.this.process(e); } } // unregister worker synchronized (workers) { if (workers.remove(this) && workers.isEmpty()) { swingPropertyChangeSupport.firePropertyChange(LOADING_PROPERTY, true, false); } } } } protected final PropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport(this, true); public void addPropertyChangeListener(PropertyChangeListener listener) { swingPropertyChangeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { swingPropertyChangeSupport.removePropertyChangeListener(listener); } }